├── README.md ├── package-lock.json ├── package.json ├── refundable.js ├── refundable2.js ├── test.html └── tests.js /README.md: -------------------------------------------------------------------------------- 1 | # Refundable ICO (RICO) prototype 2 | 3 | This repository exists to test the refundable ICO idea, 4 | described in the technical white paper for the LUKSO Blockchain. 5 | 6 | The base idea is as follows: 7 | 8 | 1. Allocation phase: Investor contribute ETH and receive ICO commitment tokens (LIA) to prrof their participation. 9 | 10 | 11 | - Investors can refund any time by sending back the LIA, and will recieve back their ETH. 12 | 13 | 14 | 2. Distribution phase: ETH flow to the ICO project over a fixed period of time 15 | 16 | - Investors can refund, but will only get the part of the ETH back, based on the current point in time, in the distribution period. 17 | - Refund works by sending the LIA back to the RICO smart contract 18 | - The RICO smart contract will send ETH and *locked* LIA (not refundable again) back to the sender. 19 | - The ratio of refund is always based on time passed inside the distribution period, meaning if an investor joins late, he will immediately only be able to refund parts of their investment. 20 | 21 | 22 | ## Manual testing 23 | 24 | Simple load the `test.html` in your browser, load the console and create a new `RICO` instance with a certain LIA/ETH ratio: 25 | 26 | ```js 27 | var rico = new RICO(1); // ETH/ LIA ratio: 1 28 | // Hardcoded: total LIA 10000, total blocks in RICO: 1000 29 | ``` 30 | 31 | And run test manually by committing from and refunding from accounts: 32 | 33 | ```js 34 | var project = { 35 | ETH: 0 36 | }; 37 | 38 | var accounts = [{ 39 | ETH: 100, 40 | LIA: 0, 41 | LIAL: 0 42 | },{ 43 | ETH: 200, 44 | LIA: 0, 45 | LIAL: 0 46 | },{ 47 | ETH: 1000, 48 | LIA: 0, 49 | LIAL: 0 50 | },{ 51 | ETH: 8000, 52 | LIA: 0, 53 | LIAL: 0 54 | }]; 55 | 56 | // commit 100 ETH 57 | rico.commit(accounts[0], 100); 58 | 59 | // check the current status 60 | rico.log(); 61 | 62 | // pass time 63 | rico.currentBlock = 500; 64 | 65 | // check the current status 66 | rico.log(); 67 | 68 | // refund 50 LIA 69 | rico.refund(accounts[0], 50); 70 | 71 | // check the current status 72 | rico.log(); 73 | ``` 74 | 75 | 76 | ## Run tests 77 | 78 | Go into your repository and run: 79 | 80 | ``` 81 | $ npm install 82 | $ npm install -g mocha // make sure mocha is globally installed 83 | ``` 84 | 85 | To run tests, run: 86 | 87 | ``` 88 | $ npm test 89 | ``` 90 | 91 | 92 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rico", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "assertion-error": { 8 | "version": "1.1.0", 9 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 10 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 11 | "dev": true 12 | }, 13 | "chai": { 14 | "version": "4.1.2", 15 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", 16 | "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", 17 | "dev": true, 18 | "requires": { 19 | "assertion-error": "1.1.0", 20 | "check-error": "1.0.2", 21 | "deep-eql": "3.0.1", 22 | "get-func-name": "2.0.0", 23 | "pathval": "1.1.0", 24 | "type-detect": "4.0.8" 25 | } 26 | }, 27 | "check-error": { 28 | "version": "1.0.2", 29 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", 30 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", 31 | "dev": true 32 | }, 33 | "deep-eql": { 34 | "version": "3.0.1", 35 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", 36 | "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", 37 | "dev": true, 38 | "requires": { 39 | "type-detect": "4.0.8" 40 | } 41 | }, 42 | "get-func-name": { 43 | "version": "2.0.0", 44 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", 45 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", 46 | "dev": true 47 | }, 48 | "pathval": { 49 | "version": "1.1.0", 50 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", 51 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", 52 | "dev": true 53 | }, 54 | "type-detect": { 55 | "version": "4.0.8", 56 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", 57 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", 58 | "dev": true 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rico", 3 | "namespace": "lukso", 4 | "version": "0.0.1", 5 | "description": "Prototype repository for the Refundable ICO", 6 | "license": "LGPL-3.0", 7 | "scripts": { 8 | "lint": "jshint *.js packages", 9 | "test": "mocha ./tests.js;" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/lukso-io/rico.git" 14 | }, 15 | "homepage": "https://github.com/lukso-io/rico", 16 | "bugs": { 17 | "url ": "https://github.com/lukso-io/rico/issues" 18 | }, 19 | "keywords": [ 20 | "Ethereum", 21 | "Refundable" 22 | ], 23 | "author": "ethereum.org", 24 | "authors": [ 25 | { 26 | "name": "Fabian Vogelsteller", 27 | "email": "fabian@lukso.io", 28 | "homepage": "https://www.lukso.io" 29 | } 30 | ], 31 | "devDependencies": { 32 | "chai": "^4.0.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /refundable.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function RICO(){ 4 | this.currentBlock = 0; 5 | this.totalBlocks = 1000; 6 | this.ethRatio = 1; 7 | this.totalLIA = 20000000; 8 | this.accounts = {}; 9 | this.project = { 10 | ETH: 0 11 | }; 12 | 13 | this.projectETH = 0; 14 | this.investorETH = 0; 15 | this.ETH = 0; 16 | 17 | this.alreadyAccountedRatio = 0; 18 | }; 19 | 20 | RICO.prototype.setCurrentBlock = function(blockNumber) { 21 | this.currentBlock = blockNumber; 22 | 23 | this._calcProjectETH(); 24 | }; 25 | 26 | RICO.prototype.drain = function(eth) { 27 | this.ETH -= eth; 28 | 29 | this._calcProjectETH(); 30 | } 31 | 32 | RICO.prototype._calcTimeRatio = function() { 33 | return this.currentBlock / this.totalBlocks; 34 | }; 35 | 36 | RICO.prototype._calcProjectETH = function() { 37 | var currentTimeRatio = this._calcTimeRatio(); 38 | 39 | // if (this.alreadyAccountedRatio !== currentTimeRatio) { 40 | 41 | // update balance 42 | this.projectETH += this.ETH * (currentTimeRatio - this.alreadyAccountedRatio); 43 | // this.projectETH = this.investorETH * currentTimeRatio; 44 | this.investorETH = this.ETH - this.projectETH; 45 | this.alreadyAccountedRatio = currentTimeRatio; 46 | // } 47 | 48 | 49 | // log 50 | console.log('-------'); 51 | console.log('Project ETH', this.projectETH); 52 | console.log('Investor ETH ', this.investorETH); 53 | 54 | console.log('Adds up? ', (this.investorETH + this.projectETH) === this.ETH); 55 | console.log('-------'); 56 | 57 | }; 58 | 59 | 60 | // INVESTOR COMMITS FUNDS 61 | RICO.prototype.commit = function(account, eth) { 62 | 63 | 64 | if (this.totalLIA - (eth * this.ethRatio) > 0) { 65 | 66 | this.accounts[account] = { 67 | LIA: eth * this.ethRatio, 68 | lockedLIA: 0, 69 | committedETH: eth, 70 | }; 71 | 72 | // update balances 73 | this.totalLIA -= this.accounts[account].LIA; 74 | this.ETH += eth; 75 | 76 | // TODO: split here already by the current ratio to project and investor??? 77 | this.investorETH += eth; 78 | 79 | // log 80 | console.log('Account '+account+' LIA ', this.accounts[account].LIA); 81 | console.log('total LIA available', this.totalLIA); 82 | } 83 | 84 | // update project eth balance 85 | this._calcProjectETH(); 86 | }; 87 | 88 | 89 | // INVESTOR REFUNDS 90 | RICO.prototype.refund = function(account, sentIct) { 91 | 92 | // update project eth balance 93 | this._calcProjectETH(); 94 | 95 | if (this.accounts[account].LIA >= ict) { 96 | 97 | 98 | 99 | } 100 | } 101 | 102 | 103 | // PROJECT WITHDRAWS ALLOCATED FUNDS 104 | RICO.prototype.projectWithdraw = function(eth) { 105 | 106 | // update project eth balance 107 | this._calcProjectETH(); 108 | 109 | 110 | if (this.projectETH >= eth && eth > 0) { 111 | 112 | // Update balance 113 | this.ETH -= eth; 114 | this.projectETH -= eth; 115 | 116 | // log 117 | console.log('Withdrawn to project ', eth); 118 | } 119 | 120 | // update project eth balance 121 | this._calcProjectETH(); 122 | }; 123 | 124 | 125 | 126 | var rico = new RICO(); 127 | 128 | 129 | rico.commit(1, 130); 130 | rico.commit(2, 180); 131 | 132 | rico.projectWithdraw(0); 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /refundable2.js: -------------------------------------------------------------------------------- 1 | 2 | function RICO(ratio, dontLog){ 3 | // constants 4 | this.dontLog = dontLog; 5 | this.ethIctRatio = ratio || 1; 6 | this.totalETH = 10000; 7 | this.totalLIA = this.totalETH * this.ethIctRatio; 8 | this.totalBlocks = 1000; // duration in blocks of the RICO 9 | 10 | this.blockNumber = 0; // since start of the RICO 11 | 12 | 13 | this.investorETH = 0; 14 | this.projectETH = 0; 15 | this.REALETH = 0; 16 | 17 | this.projectETHWithdrawn = 0; 18 | this.ethSpaceLeft = this.totalETH; 19 | 20 | 21 | // flow control 22 | this.flow = 0; 23 | this.flowLastBlock = 0; 24 | }; 25 | 26 | RICO.prototype.log = function() { 27 | 28 | // allocate funds to project before 29 | this._allocateFunds(); 30 | 31 | if (!this.dontLog) { 32 | console.log('--------'); 33 | console.log('blockNumber: ', this.blockNumber); 34 | console.log('Current Flow: ', this.flow); 35 | console.log('investorETH: ', this.investorETH); 36 | console.log('projectETH: ', this.projectETH); 37 | console.log('available for project: ', this.projectETH - this.projectETHWithdrawn); 38 | console.log('ETH in RICO: ', this.REALETH); 39 | console.log('ETH space left in RICO: ', this.ethSpaceLeft); 40 | // console.log('ETH in Project: ', project.ETH); 41 | console.log('Adds up? ', (this.investorETH + this.projectETH - this.projectETHWithdrawn) === this.REALETH); 42 | console.log('--------'); 43 | } 44 | } 45 | 46 | 47 | RICO.prototype._allocateFunds = function() { 48 | 49 | // recalculate the flow 50 | this.flow = this.investorETH / (this.totalBlocks - this.flowLastBlock); 51 | 52 | 53 | var blockPassed = this.blockNumber - this.flowLastBlock; 54 | var transferETH = Math.floor(blockPassed * this.flow); 55 | 56 | this.investorETH -= transferETH; 57 | this.projectETH += transferETH; 58 | 59 | this.ethSpaceLeft = this.totalETH - (this.investorETH + this.projectETH); 60 | 61 | // set the last flow block number 62 | this.flowLastBlock = this.blockNumber; 63 | }; 64 | 65 | 66 | // INVESTOR COMMITS FUNDS 67 | RICO.prototype.commit = function(account, eth) { 68 | 69 | // allocate funds to project before 70 | this._allocateFunds(); 71 | 72 | // CHECK that his account has the balance 73 | if (account.ETH < eth) return; 74 | 75 | // committing is only possible, if given ETH is below cap - ETH in pot 76 | if ((this.totalETH - this.REALETH) >= eth) { 77 | 78 | // update accounts balance 79 | account.ETH -= eth; 80 | account.LIA += eth * this.ethIctRatio; 81 | 82 | // update internal balances 83 | this.REALETH += eth; 84 | this.investorETH += eth; 85 | } 86 | 87 | this.log(); 88 | }; 89 | 90 | 91 | // INVESTOR REFUNDS 92 | RICO.prototype.refund = function(account, ict) { 93 | 94 | // allocate funds to project before 95 | this._allocateFunds(); 96 | 97 | // CHECK that his account has the balance 98 | if (account.LIA < ict) return; 99 | 100 | 101 | // calculate the current ratio 102 | var ratio = 1 - this.blockNumber / this.totalBlocks; 103 | // console.log('ratio', ratio); 104 | 105 | // calc the ETH refund amount 106 | var eth = Math.floor(ict / this.ethIctRatio * ratio); 107 | var ictl = Math.floor(ict * (1 - ratio)); 108 | 109 | // update accounts balance 110 | account.LIA -= ict; // !! should be account.LIA -= ict - ictl; 111 | account.LIAL += ictl; 112 | account.ETH += eth; 113 | 114 | 115 | // update internal balances 116 | this.investorETH -= eth; 117 | this.REALETH -= eth; 118 | 119 | this.log(); 120 | } 121 | 122 | 123 | // PROJECT WITHDRAWS ALLOCATED FUNDS 124 | RICO.prototype.projectWithdraw = function(project, eth) { 125 | 126 | // allocate funds to project before 127 | this._allocateFunds(); 128 | 129 | 130 | if ((this.projectETH - this.projectETHWithdrawn) >= eth && eth > 0) { 131 | 132 | // update project balance 133 | project.ETH += eth; 134 | 135 | 136 | // update internal balances 137 | this.projectETHWithdrawn += eth; 138 | this.REALETH -= eth; 139 | 140 | } 141 | 142 | this.log(); 143 | }; 144 | 145 | if (typeof window === 'undefined') 146 | module.exports = RICO; -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /tests.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var RICO = require('./refundable2.js'); 3 | var assert = chai.assert; 4 | 5 | 6 | 7 | sanityCheck = function(rico){ 8 | assert.equal((rico.investorETH + rico.projectETH - rico.projectETHWithdrawn), rico.REALETH ,'Sanity check, ETH doesn\'t add up!'); 9 | } 10 | 11 | sanityCheckAccounts = function(accounts){ 12 | 13 | accounts.forEach(function(account){ 14 | if(typeof account.shouldBe.ETH !== 'undefined') { 15 | assert.equal(account.ETH, account.shouldBe.ETH, 'ETH should be'); 16 | } 17 | if(typeof account.shouldBe.LIA !== 'undefined') { 18 | assert.equal(account.LIA, account.shouldBe.LIA, 'LIA should be'); 19 | } 20 | if(typeof account.shouldBe.LIAL !== 'undefined') { 21 | assert.equal(account.LIAL, account.shouldBe.LIAL, 'LIAL should be'); 22 | } 23 | }); 24 | } 25 | 26 | // rico.commit(1, 130); 27 | // rico.commit(2, 180); 28 | 29 | // rico.blockNumber = 500; 30 | 31 | // rico.log(); 32 | 33 | // console.log('rico.refund(2, 100)') 34 | // rico.refund(2, 100); 35 | 36 | // // rico.blockNumber = 1000; 37 | 38 | // rico.log(); 39 | 40 | 41 | var ethIctRatio = 1; 42 | 43 | 44 | 45 | describe('Refundable ICO', function() { 46 | 47 | describe('Test 1', function() { 48 | 49 | var project = { 50 | ETH: 0 51 | }; 52 | 53 | var accounts = [{ 54 | ETH: 100, 55 | LIA: 0, 56 | LIAL: 0 57 | },{ 58 | ETH: 200, 59 | LIA: 0, 60 | LIAL: 0 61 | },{ 62 | ETH: 1000, 63 | LIA: 0, 64 | LIAL: 0 65 | },{ 66 | ETH: 8000, 67 | LIA: 0, 68 | LIAL: 0 69 | }]; 70 | 71 | 72 | var rico = new RICO(ethIctRatio); 73 | 74 | describe('allocation phase', function() { 75 | it('commit and refund', function() { 76 | 77 | 78 | rico.commit(accounts[0], 100); 79 | rico.commit(accounts[1], 100); 80 | rico.commit(accounts[2], 500); 81 | 82 | assert.equal(accounts[0].ETH, 0); 83 | assert.equal(accounts[1].ETH, 100); 84 | assert.equal(accounts[2].ETH, 500); 85 | assert.equal(accounts[0].LIA / ethIctRatio, 100); 86 | assert.equal(accounts[1].LIA / ethIctRatio, 100); 87 | assert.equal(accounts[2].LIA / ethIctRatio, 500); 88 | 89 | sanityCheck(rico); 90 | 91 | rico.refund(accounts[1], 50 * ethIctRatio); 92 | rico.refund(accounts[2], 500 * ethIctRatio); 93 | 94 | assert.equal(accounts[1].LIA / ethIctRatio, 50); 95 | assert.equal(accounts[1].LIAL, 0); 96 | assert.equal(accounts[1].ETH, 150); 97 | 98 | assert.equal(accounts[2].LIA, 0); 99 | assert.equal(accounts[2].LIAL, 0); 100 | assert.equal(accounts[2].ETH, 1000); 101 | 102 | sanityCheck(rico); 103 | 104 | rico.commit(accounts[1], 100); 105 | rico.commit(accounts[2], 100); 106 | rico.commit(accounts[3], 5000); 107 | 108 | rico.refund(accounts[3], 50 * ethIctRatio); 109 | 110 | 111 | assert.equal(accounts[1].ETH, 50); 112 | assert.equal(accounts[2].ETH, 900); 113 | assert.equal(accounts[3].ETH, 3050); 114 | assert.equal(accounts[1].LIA / ethIctRatio, 150); 115 | assert.equal(accounts[2].LIA / ethIctRatio, 100); 116 | assert.equal(accounts[3].LIA / ethIctRatio, 4950); 117 | assert.equal(accounts[1].LIAL, 0); 118 | assert.equal(accounts[2].LIAL, 0); 119 | assert.equal(accounts[3].LIAL, 0); 120 | 121 | 122 | sanityCheck(rico); 123 | 124 | }); 125 | }); 126 | 127 | describe('distribution phase', function() { 128 | 129 | it('check at 25%', function() { 130 | var ratio = 0; 131 | 132 | // time passed 25% 133 | rico.blockNumber = rico.totalBlocks / 4; 134 | var ratio = rico.blockNumber / rico.totalBlocks; 135 | 136 | rico.log(); 137 | 138 | assert.equal(rico.flow, 5.3); 139 | assert.equal(rico.investorETH, 5300 * (1 - ratio)); 140 | assert.equal(rico.projectETH, 5300 * ratio); 141 | assert.equal(rico.REALETH, 5300); 142 | 143 | sanityCheck(rico); 144 | 145 | 146 | }); 147 | 148 | it('check at 50%', function() { 149 | 150 | // time passed 50% 151 | rico.blockNumber = rico.totalBlocks / 2; 152 | 153 | var ratio = rico.blockNumber / rico.totalBlocks; 154 | 155 | rico.log(); 156 | 157 | 158 | assert.equal(rico.flow, 5.3); 159 | assert.equal(rico.investorETH, 5300 * (1 - ratio)); 160 | assert.equal(rico.projectETH, 5300 * ratio); 161 | assert.equal(rico.REALETH, 5300); 162 | 163 | sanityCheck(rico); 164 | 165 | }); 166 | 167 | it('check at 75%', function() { 168 | 169 | // time passed 75% 170 | rico.blockNumber = rico.totalBlocks / 4 * 3; 171 | var ratio = rico.blockNumber / rico.totalBlocks; 172 | 173 | rico.log(); 174 | 175 | assert.equal(rico.flow, 5.3); 176 | assert.equal(rico.investorETH, 5300 * (1 - ratio)); 177 | assert.equal(rico.projectETH, 5300 * ratio); 178 | assert.equal(rico.REALETH, 5300); 179 | 180 | sanityCheck(rico); 181 | 182 | }); 183 | 184 | it('check at 100%', function() { 185 | 186 | // time passed 75% 187 | rico.blockNumber = rico.totalBlocks; 188 | var ratio = 1; 189 | 190 | rico.log(); 191 | 192 | assert.equal(rico.flow, 5.3); 193 | assert.equal(rico.investorETH, 5300 * (1 - ratio)); 194 | assert.equal(rico.projectETH, 5300 * ratio); 195 | assert.equal(rico.REALETH, 5300); 196 | 197 | sanityCheck(rico); 198 | 199 | }); 200 | }); 201 | }); 202 | 203 | describe('Test 2', function() { 204 | 205 | var project = { 206 | ETH: 0 207 | }; 208 | 209 | var accounts = [{ 210 | ETH: 100, 211 | LIA: 0, 212 | LIAL: 0 213 | },{ 214 | ETH: 200, 215 | LIA: 0, 216 | LIAL: 0 217 | },{ 218 | ETH: 2000, 219 | LIA: 0, 220 | LIAL: 0 221 | },{ 222 | ETH: 8000, 223 | LIA: 0, 224 | LIAL: 0 225 | }]; 226 | 227 | 228 | var rico = new RICO(ethIctRatio); 229 | 230 | describe('allocation phase', function() { 231 | 232 | it('commit', function() { 233 | rico.commit(accounts[0], 100); 234 | rico.commit(accounts[1], 100); 235 | rico.commit(accounts[2], 2000); 236 | rico.commit(accounts[3], 7800); 237 | 238 | assert.equal(accounts[0].ETH, 0); 239 | assert.equal(accounts[1].ETH, 100); 240 | assert.equal(accounts[2].ETH, 0); 241 | assert.equal(accounts[3].ETH, 200); 242 | assert.equal(accounts[0].LIA / ethIctRatio, 100); 243 | assert.equal(accounts[1].LIA / ethIctRatio, 100); 244 | assert.equal(accounts[2].LIA / ethIctRatio, 2000); 245 | assert.equal(accounts[3].LIA / ethIctRatio, 7800); 246 | 247 | sanityCheck(rico); 248 | }); 249 | }); 250 | 251 | describe('distribution phase', function() { 252 | 253 | it('time passes', function() { 254 | 255 | // time passed 256 | rico.blockNumber = 250; 257 | 258 | var ratio = rico.blockNumber / rico.totalBlocks; 259 | 260 | rico.log(); 261 | 262 | assert.equal(rico.flow, 10); 263 | assert.equal(rico.investorETH, 10000 * (1 - ratio)); 264 | assert.equal(rico.projectETH, 10000 * ratio); 265 | assert.equal(rico.REALETH, 10000); 266 | 267 | sanityCheck(rico); 268 | 269 | }); 270 | 271 | it('refund', function() { 272 | 273 | // time didnt passed 274 | rico.blockNumber = 250; 275 | 276 | var ratio = rico.blockNumber / rico.totalBlocks; 277 | 278 | 279 | rico.refund(accounts[3], 100 * ethIctRatio); 280 | 281 | 282 | assert.equal(rico.flow, 9.9); 283 | assert.equal(rico.investorETH, 10000 * (1 - ratio) - 100 * (1 - ratio)); 284 | assert.equal(rico.projectETH, 10000 * ratio); 285 | assert.equal(rico.REALETH, 10000 - 100 * (1 - ratio)); 286 | 287 | assert.equal(accounts[3].ETH, 200 + 100 * (1 - ratio)); 288 | 289 | sanityCheck(rico); 290 | 291 | }); 292 | 293 | 294 | it('mid-term immediate loss', function() { 295 | 296 | // time didnt passed 297 | rico.blockNumber = 300; 298 | 299 | var ratio = rico.blockNumber / rico.totalBlocks; 300 | 301 | 302 | assert.equal(accounts[1].ETH, 100); 303 | assert.equal(accounts[1].LIA, 100); 304 | 305 | rico.commit(accounts[1], 50); 306 | 307 | assert.equal(accounts[1].ETH, 50); 308 | assert.equal(accounts[1].LIA, 150); 309 | 310 | 311 | rico.refund(accounts[1], 50 * ethIctRatio); 312 | 313 | assert.equal(accounts[1].ETH, 50 + 50 * (1 - ratio)); // 70% back of 50 314 | assert.equal(accounts[1].LIA, 100); 315 | assert.equal(accounts[1].LIAL, 50 * ethIctRatio * ratio); // keeps 30% of 50 * 316 | 317 | sanityCheck(rico); 318 | 319 | }); 320 | 321 | it('check at 50%', function() { 322 | 323 | // time didnt passed 324 | rico.blockNumber = 500; 325 | 326 | var ratio = rico.blockNumber / rico.totalBlocks; 327 | 328 | // refund all tokens 329 | rico.refund(accounts[2], accounts[2].LIA); 330 | 331 | assert.equal(accounts[2].LIAL, 2000 * ethIctRatio / 2); 332 | assert.equal(accounts[2].ETH, 2000 * ethIctRatio / 2); 333 | 334 | sanityCheck(rico); 335 | 336 | }); 337 | 338 | 339 | it('over', function() { 340 | 341 | // time didnt passed 342 | rico.blockNumber = 1000; 343 | 344 | rico.log(); 345 | 346 | assert.equal(rico.flow, 7.922); 347 | assert.equal(rico.investorETH, 0); 348 | assert.equal(rico.projectETH, 8940); 349 | assert.equal(rico.REALETH, 8940); 350 | 351 | sanityCheck(rico); 352 | 353 | }); 354 | }); 355 | }); 356 | 357 | describe('Test 3', function() { 358 | 359 | var project = { 360 | ETH: 0 361 | }; 362 | 363 | var accounts = [{ 364 | ETH: 100, 365 | LIA: 0, 366 | LIAL: 0, 367 | COMMITS: 0, 368 | RETURNS: 0, 369 | shouldBe: {} 370 | },{ 371 | ETH: 200, 372 | LIA: 0, 373 | LIAL: 0, 374 | COMMITS: 0, 375 | RETURNS: 0, 376 | shouldBe: {} 377 | },{ 378 | ETH: 2000, 379 | LIA: 0, 380 | LIAL: 0, 381 | COMMITS: 0, 382 | RETURNS: 0, 383 | shouldBe: {} 384 | },{ 385 | ETH: 8000, 386 | LIA: 0, 387 | LIAL: 0, 388 | COMMITS: 0, 389 | RETURNS: 0, 390 | shouldBe: {} 391 | }]; 392 | 393 | 394 | var rico = new RICO(ethIctRatio); 395 | 396 | describe('allocation phase', function() { 397 | 398 | it('commit', function() { 399 | 400 | var ratio = rico.blockNumber / rico.totalBlocks; 401 | 402 | accounts[0].shouldBe.ETH = accounts[0].ETH - 100; 403 | accounts[1].shouldBe.ETH = accounts[1].ETH - 100; 404 | accounts[2].shouldBe.ETH = accounts[2].ETH - 2000; 405 | accounts[3].shouldBe.ETH = accounts[3].ETH - 7800; 406 | accounts[0].shouldBe.LIA = accounts[0].LIA + 100; 407 | accounts[1].shouldBe.LIA = accounts[1].LIA + 100; 408 | accounts[2].shouldBe.LIA = accounts[2].LIA + 2000; 409 | accounts[3].shouldBe.LIA = accounts[3].LIA + 7800; 410 | 411 | // COMMIT 412 | rico.commit(accounts[0], 100); 413 | rico.commit(accounts[1], 100); 414 | rico.commit(accounts[2], 2000); 415 | rico.commit(accounts[3], 7800); 416 | 417 | accounts[0].COMMITS += 100; 418 | accounts[1].COMMITS += 100; 419 | accounts[2].COMMITS += 2000; 420 | accounts[3].COMMITS += 7800; 421 | 422 | sanityCheckAccounts(accounts); 423 | sanityCheck(rico); 424 | }); 425 | }); 426 | 427 | describe('distribution phase', function() { 428 | 429 | it('time passes', function() { 430 | 431 | // time passed 432 | rico.blockNumber = 250; 433 | 434 | var ratio = rico.blockNumber / rico.totalBlocks; 435 | 436 | rico.log(); 437 | 438 | assert.equal(rico.flow, 10); 439 | assert.equal(rico.investorETH, 10000 * (1 - ratio)); 440 | assert.equal(rico.projectETH, 10000 * ratio); 441 | assert.equal(rico.REALETH, 10000); 442 | 443 | sanityCheck(rico); 444 | 445 | }); 446 | 447 | it('refund', function() { 448 | 449 | // time didnt passed 450 | rico.blockNumber = 250; 451 | 452 | var ratio = rico.blockNumber / rico.totalBlocks; 453 | 454 | accounts[3].shouldBe.ETH = accounts[3].ETH + 100 * ethIctRatio * (1 - ratio); 455 | accounts[3].shouldBe.LIA = accounts[3].LIA - 100; 456 | 457 | // REFUND 458 | rico.refund(accounts[3], 100 * ethIctRatio); 459 | 460 | accounts[3].RETURNS += 100; 461 | 462 | 463 | assert.equal(rico.flow, 9.9); 464 | assert.equal(rico.investorETH, 10000 * (1 - ratio) - 100 * (1 - ratio)); 465 | assert.equal(rico.projectETH, 10000 * ratio); 466 | assert.equal(rico.REALETH, 10000 - 100 * (1 - ratio)); 467 | 468 | 469 | sanityCheckAccounts(accounts); 470 | sanityCheck(rico); 471 | 472 | }); 473 | 474 | 475 | it('mid-term immediate loss', function() { 476 | 477 | // time passed 478 | rico.blockNumber = 300; 479 | 480 | var ratio = rico.blockNumber / rico.totalBlocks; 481 | 482 | assert.equal(accounts[1].ETH, 100); 483 | assert.equal(accounts[1].LIA, 100); 484 | assert.equal(accounts[1].LIAL, 0); 485 | 486 | accounts[1].shouldBe.ETH = accounts[1].ETH - 50; 487 | accounts[1].shouldBe.LIA = accounts[1].LIA + 50 / ethIctRatio; 488 | 489 | // COMMIT 490 | rico.commit(accounts[1], 50); 491 | 492 | accounts[1].COMMITS += 50; 493 | 494 | sanityCheckAccounts(accounts); 495 | 496 | assert.equal(accounts[1].ETH, 50); 497 | assert.equal(accounts[1].LIA, 150); 498 | 499 | accounts[1].shouldBe.ETH = accounts[1].ETH + 50 * ethIctRatio * (1 - ratio); 500 | accounts[1].shouldBe.LIA = accounts[1].LIA - 50 / ethIctRatio; 501 | 502 | // REFUND 503 | rico.refund(accounts[1], 50 * ethIctRatio); 504 | 505 | accounts[1].RETURNS += 50; 506 | 507 | assert.equal(accounts[1].ETH, 50 + 50 * (1 - ratio)); // 70% back of 50 508 | assert.equal(accounts[1].LIA, 100); 509 | assert.equal(accounts[1].LIAL, 50 * ethIctRatio * ratio); // keeps 30% of 50 * 510 | 511 | sanityCheckAccounts(accounts); 512 | 513 | accounts[3].shouldBe.ETH = accounts[3].ETH + Math.floor(2000 * ethIctRatio * (1 - ratio)); 514 | accounts[3].shouldBe.LIA = accounts[3].LIA - 2000 / ethIctRatio; 515 | 516 | // REFUND 517 | rico.refund(accounts[3], 2000 * ethIctRatio); 518 | 519 | accounts[3].RETURNS += 2000; 520 | 521 | sanityCheckAccounts(accounts); 522 | sanityCheck(rico); 523 | 524 | }); 525 | 526 | it('check at 50%', function() { 527 | 528 | // time passed 529 | rico.blockNumber = 500; 530 | 531 | var ratio = rico.blockNumber / rico.totalBlocks; 532 | 533 | accounts[2].shouldBe.ETH = accounts[2].ETH + accounts[2].LIA * ethIctRatio * (1 - ratio); 534 | accounts[2].shouldBe.LIA = accounts[2].LIA - accounts[2].LIA / ethIctRatio; 535 | 536 | // REFUND all tokens 537 | accounts[2].RETURNS += accounts[2].LIA; 538 | rico.refund(accounts[2], accounts[2].LIA); 539 | 540 | 541 | assert.equal(accounts[2].LIAL, 2000 * ethIctRatio / 2); 542 | assert.equal(accounts[2].ETH, 2000 * ethIctRatio / 2); 543 | 544 | sanityCheckAccounts(accounts); 545 | sanityCheck(rico); 546 | 547 | }); 548 | 549 | it('check at 75%', function() { 550 | 551 | // time passed 552 | rico.blockNumber = 750; 553 | 554 | var ratio = rico.blockNumber / rico.totalBlocks; 555 | 556 | accounts[3].shouldBe.ETH = accounts[3].ETH - 275; 557 | accounts[3].shouldBe.LIA = accounts[3].LIA + 275 / ethIctRatio; 558 | 559 | // COMMIT 560 | rico.commit(accounts[3], 275); 561 | 562 | accounts[3].COMMITS += 275; 563 | 564 | sanityCheckAccounts(accounts); 565 | sanityCheck(rico); 566 | 567 | }); 568 | 569 | it('check at 78%', function() { 570 | 571 | // time passed 572 | rico.blockNumber = 780; 573 | 574 | var ratio = rico.blockNumber / rico.totalBlocks; 575 | 576 | accounts[3].shouldBe.ETH = accounts[3].ETH + Math.floor(20 * ethIctRatio * (1 - ratio)); 577 | accounts[3].shouldBe.LIA = accounts[3].LIA - 20 / ethIctRatio; 578 | 579 | // REFUND 580 | rico.refund(accounts[3], 20 * ethIctRatio); 581 | 582 | accounts[3].RETURNS += 20; 583 | 584 | 585 | sanityCheckAccounts(accounts); 586 | sanityCheck(rico); 587 | 588 | }); 589 | 590 | it('check at 78%', function() { 591 | 592 | // time passed 593 | rico.blockNumber = 780; 594 | 595 | var ratio = rico.blockNumber / rico.totalBlocks; 596 | 597 | accounts[3].shouldBe.ETH = accounts[3].ETH + Math.floor(20 * ethIctRatio * (1 - ratio)); 598 | accounts[3].shouldBe.LIA = accounts[3].LIA - 20 / ethIctRatio; 599 | 600 | // REFUND 601 | rico.refund(accounts[3], 20 * ethIctRatio); 602 | 603 | accounts[3].RETURNS += 20; 604 | 605 | 606 | sanityCheckAccounts(accounts); 607 | sanityCheck(rico); 608 | 609 | }); 610 | 611 | it('check at 79%', function() { 612 | 613 | // time passed 614 | rico.blockNumber = 790; 615 | 616 | var ratio = rico.blockNumber / rico.totalBlocks; 617 | 618 | accounts[3].shouldBe.ETH = accounts[3].ETH + Math.floor(45 * ethIctRatio * (1 - ratio)); 619 | accounts[3].shouldBe.LIA = accounts[3].LIA - 45 / ethIctRatio; 620 | 621 | // REFUND 622 | rico.refund(accounts[3], 45 * ethIctRatio); 623 | 624 | accounts[3].RETURNS += 45; 625 | 626 | 627 | sanityCheckAccounts(accounts); 628 | sanityCheck(rico); 629 | 630 | }); 631 | 632 | it('check at 81%', function() { 633 | 634 | // time passed 635 | rico.blockNumber = 810; 636 | 637 | var ratio = rico.blockNumber / rico.totalBlocks; 638 | 639 | accounts[3].shouldBe.ETH = accounts[3].ETH + Math.floor(52 * ethIctRatio * (1 - ratio)); 640 | accounts[3].shouldBe.LIA = accounts[3].LIA - 52 / ethIctRatio; 641 | 642 | // REFUND 643 | rico.refund(accounts[3], 52 * ethIctRatio); 644 | 645 | accounts[3].RETURNS += 52; 646 | 647 | 648 | sanityCheckAccounts(accounts); 649 | sanityCheck(rico); 650 | 651 | }); 652 | 653 | it('check at 81%', function() { 654 | 655 | // time passed 656 | rico.blockNumber = 810; 657 | 658 | var ratio = rico.blockNumber / rico.totalBlocks; 659 | 660 | accounts[3].shouldBe.ETH = accounts[3].ETH - 1000; 661 | accounts[3].shouldBe.LIA = accounts[3].LIA + 1000 / ethIctRatio; 662 | 663 | // COMMIT 664 | rico.commit(accounts[3], 1000); 665 | 666 | accounts[3].COMMITS += 1000; 667 | 668 | 669 | sanityCheckAccounts(accounts); 670 | sanityCheck(rico); 671 | 672 | }); 673 | 674 | 675 | it('over', function() { 676 | 677 | // time didnt passed 678 | rico.blockNumber = 1000; 679 | 680 | rico.log(); 681 | 682 | // assert.equal(accounts[1].LIAL, accounts[1].LIA - accounts[1].RETURNS + accounts[1].LIA); 683 | // assert.equal(accounts[2].LIAL, accounts[2].LIA - accounts[2].RETURNS); 684 | // assert.equal(accounts[3].LIAL, accounts[3].LIA - accounts[3].RETURNS); 685 | 686 | console.log(accounts); 687 | 688 | assert.equal(rico.investorETH, 0); 689 | assert.equal(rico.projectETH, 8789); 690 | assert.equal(rico.REALETH, 8789); 691 | assert.equal(rico.flow, 12.16842105263158); 692 | 693 | sanityCheck(rico); 694 | 695 | }); 696 | }); 697 | }); 698 | }); --------------------------------------------------------------------------------