├── .gitignore ├── truffle-config.js ├── migrations ├── 1_initial_migration.js └── 2_deploy_contract.js ├── index.js ├── contracts ├── Migrations.sol └── KriptoLottery.sol ├── package.json ├── .vscode └── launch.json ├── truffle.js ├── README.md └── test └── lottery-tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | /deploy-* 4 | bin 5 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // See 3 | // to customize your Truffle configuration! 4 | }; 5 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy_contract.js: -------------------------------------------------------------------------------- 1 | const KriptoLottery = artifacts.require('./KriptoLottery.sol'); 2 | 3 | module.exports = function(deployer) { 4 | return deployer.then(() => { 5 | return deployer.deploy(KriptoLottery).then(instance => { 6 | confirmationAddress = KriptoLottery.address; 7 | }); 8 | }); 9 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var express = require("express"), 2 | app = new express(); 3 | 4 | app.set('port', (process.env.PORT || 5000)); 5 | 6 | app.use("/ui", express.static("ui")); 7 | app.use("/contracts", express.static("build/contracts")); 8 | 9 | app.listen(app.get('port'), function() { 10 | console.log('Node app is running on port', app.get('port')); 11 | }); 12 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.17; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | function Migrations() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crypto-lottery", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "prestart": "truffle compile", 8 | "start": "node index.js", 9 | "test": "truffle test", 10 | "compile": "truffle compile", 11 | "migrate": "truffle migrate" 12 | }, 13 | "author": "Mert Susur", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "truffle-hdwallet-provider": "0.0.3", 17 | "yargs": "^10.0.3" 18 | }, 19 | "dependencies": { 20 | "express": "^4.16.2", 21 | "truffle": "^4.0.4", 22 | "zeppelin-solidity": "^1.5.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceFolder}/index.js" 12 | }, 13 | { 14 | "type": "node", 15 | "request": "launch", 16 | "name": "Test", 17 | "program": "${workspaceFolder}/node_modules/.bin/truffle", 18 | "args": [ 19 | "test" 20 | ] 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | const yargs = require('yargs'); 2 | var provider, address; 3 | 4 | if (yargs.argv.network == 'rinkeby' || yargs.argv.network == 'mainnet') { 5 | var providerURL = `https://${yargs.argv.network}.infura.io/${yargs.argv.accessToken}`; 6 | var HDWalletProvider = require('truffle-hdwallet-provider'); 7 | var mnemonic = yargs.argv.mnemonic; 8 | 9 | provider = new HDWalletProvider(mnemonic, providerURL, 0); 10 | address = "0x" + provider.wallet.getAddress().toString("hex"); 11 | console.log('Provider address', address); 12 | console.log('Deploying to ', providerURL); 13 | } 14 | 15 | module.exports = { 16 | networks: { 17 | rinkeby: { 18 | gasPrice: 800000000000, // 80 gwei, 19 | provider: provider, 20 | network_id: 3, 21 | from: address 22 | }, 23 | mainnet: { 24 | gas: 2550000, 25 | gasPrice: 1000000000, // 1 gwei 26 | provider: provider, 27 | network_id: 1, 28 | from: address 29 | } 30 | } 31 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Krypto-Lottery 2 | ===== 3 | 4 | A basic lottery game implementation on Ethereum blockchain. Lotteries are heavily regulated by the governments, and aim of this project is to show people how easily you can build a lottery system on blockchain without trusting anyone and without spending millions of dollars for building a secure system. 5 | 6 | ## How does it work? 7 | You need to deploy the [KriptoLottery](./contracts/KriptoLottery.sol) contract to an Ethereum network then you'll have the following features; 8 | - Anyone can send send money to participate to the lottery as many times as they want. 9 | - Default amount is `0.02 ether` unless you state otherwise in the contract constructor. 10 | - Owner can set a ratio to send some or all of the money to a charity account. 11 | - Owner can set a ratio to send some or all of the money to an affiliate account. 12 | - Owner can call `runLottery` function to select a random winner and transfer the balance to charity, affiliate and to the winner. (See [Only owner can run the lottery section for more details](#Only-owner-can-run-the-lottery)) 13 | 14 | ## What's missing? 15 | ### Randomness 16 | 17 | [Definition from wikipedia](https://en.wikipedia.org/wiki/Randomness) 18 | >Randomness is the lack of pattern or predictability in events. A random sequence of events, symbols or steps has no order and does not follow an intelligible pattern or combination. 19 | 20 | [From Ethereum yellow paper](http://gavwood.com/paper.pdf) 21 | >Providing random numbers within a deterministic system is, naturally, an impossible task. However, we can approximate with pseudo-random numbers by utilizing data which is generally unknowable at the time of transacting. Such data might include the block’s hash, the block’s timestamp, and the block’s beneficiary address. In order to make it hard for a malicious miner to control those values, one should use the BLOCKHASH operation in order to use hashes of the previous 256 blocks as pseudo-random numbers. For a series of such numbers, a trivial solution would be to add some constant amount and hashing the result. 22 | 23 | With the descriptions in mind I've used the [solution described by rolandkofler](https://github.com/rolandkofler/ether-entrophy/blob/master/BlockHash2RNG.sol). 24 | 25 | Another solution to the randomness could be using an [off the chain random generator](https://blog.oraclize.it/the-random-datasource-chapter-2-779946e54f49) with an oracle. 26 | 27 | ### User interface 28 | Contract accepts payments and adds everyone to the list of participants. There is no user interface needed. 29 | 30 | ### Only owner can run the lottery 31 | As the title states, only owner can run the lottery whereas in a real **trustless** system anyone should be able to run the lottery. Even though this is very easy to implement, it's not that easy to unit test. Unfortunately, I am leaving this feature out until the tests are ready. 32 | 33 | ## Building the application 34 | Project is using [truffle](truffleframework.com) to compile and deploy the contracts therefore you need to have Nodejs installed. 35 | 36 | Run `npm install` to install the dependencies then you can run `npm test` to run the unit tests. 37 | 38 | ## Installing the application to an Ethereum network 39 | Truffle commands helps you to install the contracts to a network. In this project it's slightly different but more useful. You can either use `npm run migrate` or alternatively if truffle is installed globally you can `truffle migrate` directly with the following arguments. These configurations can be updated by modifying the `./truffle-config.js` 40 | 41 | `--network`: name of the network you want to deploy to. You can use `rinkeby` or `mainnet` depends on which one you would like to deploy the contract. 42 | 43 | `--accessToken`: Deployment is using [infura](https://infura.io/) to deploy the contract. In order to deploy the contract you need to create an account, obtain the access token, and pass as an argument. 44 | 45 | `--mnemonic`: 12 word mnemonic for the deployment account. Your account must have enough ether to cover the gas cost. 46 | 47 | ``` 48 | npm run migrate -- --network <> --accessToken <> --mnemonic <<12 word mnemonic>> 49 | ``` 50 | 51 | ## Contribute 52 | 53 | I :heart: to see people contributing to this project in any way they can! Also you can reach out to me from twitter [@Mertsusur](https://twitter.com/MertSusur). 54 | 55 | I need help; 56 | - for testing the contract. 57 | - for oraclize the randomness. 58 | - unit testing the trustless `runLottery` function. 59 | - adding extra features or maybe even better math calculations. 60 | -------------------------------------------------------------------------------- /contracts/KriptoLottery.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import 'node_modules/zeppelin-solidity/contracts/ownership/Ownable.sol'; 4 | import 'node_modules/zeppelin-solidity/contracts/lifecycle/Pausable.sol'; 5 | 6 | contract KriptoLottery is Ownable, Pausable { 7 | // wait for approx. 1 week. 8 | uint constant NEXT_LOTTERY_WAIT_TIME_IN_BLOCKS = 38117; 9 | 10 | event LotteryRunFinished(address winner, uint256 charityAmount, uint256 affiliateAmount, uint256 jackpot); 11 | event ApplicationDone(uint applicationNumber); 12 | 13 | uint128 public maxParticipant; 14 | uint256 public lotteryAmount; 15 | address public charityAddress; 16 | address public affiliateAddress; 17 | uint256 public totalGivenAmount; 18 | uint256 public totalDonation; 19 | uint256 public totalAffiliateAmount; 20 | uint16 public donationRatio; 21 | uint16 public affiliateRatio; 22 | 23 | mapping(uint => LotteryPlay) public lotteries; 24 | uint16 public currentLottery; 25 | 26 | struct LotteryPlay { 27 | uint endBlock; 28 | uint startBlock; 29 | bytes32 blockHash; 30 | address winner; 31 | Participant[] participants; 32 | } 33 | 34 | struct Participant { 35 | address addr; 36 | } 37 | 38 | function KriptoLottery(uint128 _maxParticipant, uint256 _lotteryAmount) public { 39 | if (_maxParticipant == 0) { 40 | maxParticipant = 30; 41 | } else { 42 | maxParticipant = _maxParticipant; 43 | } 44 | 45 | if (_lotteryAmount == 0) { 46 | lotteryAmount = 0.02 ether; 47 | } else { 48 | lotteryAmount = _lotteryAmount; 49 | } 50 | paused = true; 51 | initialise(); 52 | paused = false; 53 | affiliateAddress = msg.sender; 54 | charityAddress = msg.sender; 55 | donationRatio = 100; 56 | affiliateRatio = 0; 57 | } 58 | 59 | function() public payable { 60 | apply(); 61 | } 62 | 63 | function apply() public payable whenNotPaused returns (uint256) { 64 | // Anyone can apply as much as they want. 65 | require(lotteries[currentLottery].participants.length + 1 <= maxParticipant); 66 | require(msg.value == lotteryAmount); 67 | 68 | lotteries[currentLottery].participants.push(Participant(msg.sender)); 69 | ApplicationDone(lotteries[currentLottery].participants.length - 1); 70 | return lotteries[currentLottery].participants.length - 1; 71 | } 72 | 73 | function getCurrentCount() public constant returns (uint256) { 74 | return lotteries[currentLottery].participants.length; 75 | } 76 | 77 | function getCurrentLottery() public constant returns(uint endBlock, uint startBlock, bytes32 blockHash, address winner, uint participants) { 78 | LotteryPlay storage lottery = lotteries[currentLottery]; 79 | return (lottery.endBlock, lottery.startBlock, lottery.blockHash, lottery.winner, lottery.participants.length); 80 | } 81 | 82 | // Admin tools 83 | 84 | function initialise() public whenPaused onlyOwner { 85 | // Balance should be 0 in order to start a new lottery. 86 | // otherwise you might end up **stealing** others money. 87 | require(this.balance == 0); 88 | currentLottery++; 89 | lotteries[currentLottery].startBlock = block.number; 90 | lotteries[currentLottery].blockHash = block.blockhash(lotteries[currentLottery].startBlock); 91 | // Set the next waiting time to apprx. 1 week. 92 | // This is not working since I couldn't find a good way to unit test this. 93 | lotteries[currentLottery].endBlock = block.number + NEXT_LOTTERY_WAIT_TIME_IN_BLOCKS; 94 | } 95 | 96 | function setParticipantsNumber(uint128 newNumber) public onlyOwner { 97 | maxParticipant = newNumber; 98 | } 99 | 100 | function setAffiliateRatio(uint16 newRatio) public onlyOwner { 101 | require(newRatio < 101); 102 | require(newRatio + donationRatio < 101); 103 | affiliateRatio = newRatio; 104 | } 105 | 106 | function setAffiliateAddress(address _newAffiliate) public onlyOwner { 107 | require(_newAffiliate != address(0)); 108 | affiliateAddress = _newAffiliate; 109 | } 110 | 111 | function setCharityAddress(address _newCharityAddress) public onlyOwner { 112 | require(_newCharityAddress != address(0)); 113 | charityAddress = _newCharityAddress; 114 | } 115 | 116 | function setDonationRatio(uint16 newRatio) public onlyOwner { 117 | require(newRatio < 101); 118 | require(newRatio + affiliateRatio < 101); 119 | donationRatio = newRatio; 120 | } 121 | 122 | function runLottery() public onlyOwner returns (uint256, address) { 123 | require(charityAddress != address(0)); 124 | require(lotteries[currentLottery].participants.length >= 2); 125 | // Uncomment the line below, *if* you can find a way to unit test 126 | // this logic. 127 | // require(lotteries[currentLottery].endBlock < block.number); 128 | paused = true; 129 | 130 | // send money to charity account. 131 | uint256 charityAmount = (this.balance * donationRatio) / 100; 132 | charityAddress.transfer(charityAmount); 133 | totalDonation += charityAmount; 134 | 135 | // send money to an affiliate address to cover the costs. 136 | uint256 affiliateAmount = (this.balance * affiliateRatio) / 100; 137 | affiliateAddress.transfer(affiliateAmount); 138 | totalAffiliateAmount += affiliateAmount; 139 | 140 | // random winner. 141 | uint256 randomValue = random(); 142 | address winner = lotteries[currentLottery].participants[randomValue].addr; 143 | 144 | // send the rest of the funds to the winner if anything is left. 145 | uint256 winningPrice = this.balance; 146 | if (winningPrice > 0) { 147 | winner.transfer(winningPrice); 148 | } 149 | 150 | lotteries[currentLottery].winner = winner; 151 | totalGivenAmount += winningPrice; 152 | 153 | // initialise a new one. 154 | initialise(); 155 | paused = false; 156 | LotteryRunFinished(winner, charityAmount, affiliateAmount, winningPrice); 157 | return (randomValue, winner); 158 | } 159 | 160 | // Helper functions 161 | 162 | function random() internal view returns(uint256) { 163 | // I know, I know. I should have use a proper off the chain random generator. 164 | // Why not implement oraclize and send a pull request? 165 | uint256 r1 = uint256(block.blockhash(block.number-1)); 166 | uint256 r2 = uint256(block.blockhash(lotteries[currentLottery].startBlock)); 167 | 168 | uint256 val; 169 | 170 | assembly { 171 | val := xor(r1, r2) 172 | } 173 | return val % lotteries[currentLottery].participants.length; 174 | } 175 | 176 | } -------------------------------------------------------------------------------- /test/lottery-tests.js: -------------------------------------------------------------------------------- 1 | const KriptoLottery = artifacts.require("KriptoLottery"); 2 | 3 | const isRevertError = (error) => { 4 | const invalidOpcode = error.message.search('invalid opcode') >= 0; 5 | const outOfGas = error.message.search('out of gas') >= 0; 6 | const revert = error.message.search('revert') >= 0; 7 | return invalidOpcode || outOfGas || revert; 8 | } 9 | 10 | contract('KriptoLottery', accounts => { 11 | let lottery; 12 | const owner = accounts[0], 13 | someone = accounts[1], 14 | someone2 = accounts[2], 15 | charity = accounts[3], 16 | affiliate = accounts[4]; 17 | 18 | describe('constructor', () => { 19 | it('should set the participant number 30 by default', async() => { 20 | lottery = await KriptoLottery.new(); 21 | 22 | const maxCount = await lottery.maxParticipant.call(); 23 | 24 | assert.equal(30, maxCount); 25 | }); 26 | 27 | it('should set the participant count if value exists', async() => { 28 | lottery = await KriptoLottery.new(100, 0); 29 | 30 | const maxCount = await lottery.maxParticipant.call(); 31 | 32 | assert.equal(100, maxCount); 33 | }); 34 | 35 | it('should set the owner', async() => { 36 | lottery = await KriptoLottery.new(); 37 | 38 | const ownerAddress = await lottery.owner.call(); 39 | 40 | assert.equal(owner, ownerAddress); 41 | }); 42 | 43 | it('should set the lottery amount to 0.02 eth by default', async() => { 44 | lottery = await KriptoLottery.new(); 45 | 46 | const amount = await lottery.lotteryAmount.call(); 47 | 48 | assert.equal(web3.toWei(0.02, 'ether'), amount); 49 | }); 50 | 51 | it('should set the lottery amount if the value exists', async() => { 52 | lottery = await KriptoLottery.new(10, web3.toWei(1, 'ether')); 53 | 54 | const amount = await lottery.lotteryAmount.call(); 55 | 56 | assert.equal(web3.toWei(1, 'ether'), amount); 57 | }); 58 | 59 | it('should set the current lottery to one', async() => { 60 | lottery = await KriptoLottery.new(10, web3.toWei(1, 'ether')); 61 | 62 | const amount = await lottery.currentLottery.call(); 63 | 64 | assert.equal(1, amount); 65 | }); 66 | 67 | it('should initialise the first lottery', async() => { 68 | lottery = await KriptoLottery.new(); 69 | 70 | const currentLottery = await lottery.getCurrentLottery(); 71 | 72 | assert.ok(currentLottery[0] > 0); 73 | }); 74 | 75 | it('should set the affiliate address as owner', async() => { 76 | lottery = await KriptoLottery.new(); 77 | 78 | const affiliateAddress = await lottery.affiliateAddress.call(); 79 | 80 | assert.equal(owner, affiliateAddress); 81 | }); 82 | 83 | it('should set the charity address as owner', async() => { 84 | lottery = await KriptoLottery.new(); 85 | 86 | const charityAddress = await lottery.charityAddress.call(); 87 | 88 | assert.equal(owner, charityAddress); 89 | }); 90 | 91 | it('should initialise the donation ratio to 100%', async() => { 92 | lottery = await KriptoLottery.new(); 93 | 94 | const ratio = await lottery.donationRatio.call(); 95 | 96 | assert.equal(100, ratio); 97 | }); 98 | 99 | it('should initialise the affiliate ratio to 0', async() => { 100 | lottery = await KriptoLottery.new(); 101 | 102 | const ratio = await lottery.affiliateRatio.call(); 103 | 104 | assert.equal(0, ratio); 105 | }); 106 | }); 107 | 108 | describe('lottery functions', () => { 109 | beforeEach(async() => { 110 | lottery = await KriptoLottery.new(30, 0); 111 | }); 112 | 113 | describe('setMaxParticipants', () => { 114 | it('should increase the max number of people', async() => { 115 | await lottery.setParticipantsNumber(500); 116 | 117 | const newMax = await lottery.maxParticipant.call(); 118 | 119 | assert.equal(500, newMax); 120 | }); 121 | 122 | it('should not set the max number of people if it was not the owner', async() => { 123 | try { 124 | await lottery.setParticipantsNumber(500, { from: someone }); 125 | } catch (error) { 126 | assert.equal(isRevertError(error), true); 127 | return; 128 | } 129 | 130 | // Fail 131 | assert.fail('Failed'); 132 | }); 133 | }); 134 | 135 | describe('apply function', () => { 136 | it('should not work when paused', async() => { 137 | await lottery.pause(); 138 | 139 | try { 140 | await lottery.apply(); 141 | } catch (error) { 142 | assert.equal(isRevertError(error), true); 143 | return; 144 | } 145 | 146 | // Fail 147 | assert.fail('Failed'); 148 | }); 149 | 150 | it('should increase the balance by lottery payment amount', async() => { 151 | await lottery.apply({ value: web3.toWei(0.02, 'ether') }); 152 | 153 | const balance = web3.eth.getBalance(lottery.address).toNumber(); 154 | 155 | assert.equal(web3.toWei(0.02, 'ether'), balance); 156 | }); 157 | 158 | it('should increase the currentCount by 1', async() => { 159 | await lottery.apply({ value: web3.toWei(0.02, 'ether'), from: someone }); 160 | 161 | const currentCount = await lottery.getCurrentCount(); 162 | 163 | assert.equal(1, currentCount); 164 | }); 165 | 166 | it('should allow multiple application from the same person', async() => { 167 | await lottery.apply({ value: web3.toWei(0.02, 'ether'), from: someone }); 168 | await lottery.apply({ value: web3.toWei(0.02, 'ether'), from: someone }); 169 | 170 | const currentCount = await lottery.getCurrentCount(); 171 | 172 | assert.equal(2, currentCount); 173 | }); 174 | 175 | it('should not work when max number of people exceeds', async() => { 176 | lottery = await KriptoLottery.new(2, 0); 177 | 178 | await lottery.apply({ value: web3.toWei(0.02, 'ether'), from: someone }); 179 | await lottery.apply({ value: web3.toWei(0.02, 'ether'), from: someone2 }); 180 | 181 | try { 182 | await lottery.apply({ value: web3.toWei(0.02, 'ether'), from: owner }); 183 | } catch (error) { 184 | assert.equal(isRevertError(error), true); 185 | return; 186 | } 187 | 188 | // Fail 189 | assert.fail('Failed'); 190 | }); 191 | 192 | it('should not work when the payment is less than the deposit', async() => { 193 | 194 | try { 195 | await lottery.apply({ value: web3.toWei(0.0199, 'ether') }); 196 | } catch (error) { 197 | assert.equal(isRevertError(error), true); 198 | return; 199 | } 200 | 201 | // Fail 202 | assert.fail('Failed'); 203 | }); 204 | 205 | it('should not work when the payment is more than the deposit', async() => { 206 | 207 | try { 208 | await lottery.apply({ value: web3.toWei(0.3, 'ether') }); 209 | } catch (error) { 210 | assert.equal(isRevertError(error), true); 211 | return; 212 | } 213 | 214 | // Fail 215 | assert.fail('Failed'); 216 | }); 217 | }); 218 | 219 | describe('setAffiliateAddress', () => { 220 | it('should only run by owner', async() => { 221 | try { 222 | await lottery.setAffiliateAddress(affiliate, { from: someone }); 223 | } catch (error) { 224 | assert.ok(isRevertError(error)); 225 | return; 226 | } 227 | assert.fail('failed'); 228 | }); 229 | 230 | it('should require the address is not null', async() => { 231 | try { 232 | await lottery.setAffiliateAddress(null, { from: someone }); 233 | } catch (error) { 234 | assert.ok(isRevertError(error)); 235 | return; 236 | } 237 | assert.fail('failed'); 238 | }); 239 | 240 | it('should set affiliate address', async() => { 241 | await lottery.setAffiliateAddress(affiliate, { from: owner }); 242 | 243 | const affiliateAddress = await lottery.affiliateAddress.call(); 244 | 245 | assert.equal(affiliate, affiliateAddress); 246 | }); 247 | }); 248 | 249 | describe('default function', () => { 250 | it('should accept payment and increase participants by one', async() => { 251 | await lottery.sendTransaction({ value: web3.toWei(0.02, 'ether'), from: someone }); 252 | 253 | const currentCount = await lottery.getCurrentCount(); 254 | const balance = web3.eth.getBalance(lottery.address).toNumber(); 255 | 256 | assert.equal(1, currentCount); 257 | assert.equal(web3.toWei(0.02, 'ether'), balance); 258 | }); 259 | }); 260 | 261 | describe('run lottery', () => { 262 | it('should only run by owner', async() => { 263 | try { 264 | await lottery.runLottery({ from: someone }); 265 | } catch (error) { 266 | assert.equal(isRevertError(error), true); 267 | return; 268 | } 269 | assert.fail('failed'); 270 | }); 271 | 272 | it('should require charity address not null', async() => { 273 | try { 274 | await lottery.runLottery({ from: owner }); 275 | } catch (error) { 276 | assert.ok(isRevertError(error)); 277 | return; 278 | } 279 | assert.fail('failed'); 280 | }); 281 | 282 | it('should not run with less than two participant', async() => { 283 | try { 284 | // try with zero participant 285 | await lottery.runLottery({ from: owner }); 286 | } catch (error) { 287 | assert.ok(isRevertError(error)); 288 | return; 289 | } 290 | 291 | await lottery.apply({ value: web3.toWei(0.02, 'ether') }); 292 | try { 293 | // try with one participant 294 | await lottery.runLottery({ from: owner }); 295 | } catch (error) { 296 | assert.ok(isRevertError(error)); 297 | return; 298 | } 299 | assert.fail('failed'); 300 | }); 301 | 302 | it('should unpause the contract', async() => { 303 | await lottery.setCharityAddress(charity, { from: owner }); 304 | await lottery.apply({ value: web3.toWei(0.02, 'ether') }); 305 | await lottery.apply({ value: web3.toWei(0.02, 'ether') }); 306 | await lottery.apply({ value: web3.toWei(0.02, 'ether') }); 307 | 308 | await lottery.runLottery({ from: owner }); 309 | 310 | const paused = await lottery.paused.call(); 311 | 312 | assert.ok(!paused); 313 | }); 314 | 315 | it('should get 10% cut to charity account', async() => { 316 | 317 | lottery = await KriptoLottery.new(5, web3.toWei(5, 'ether')); 318 | 319 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 320 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 321 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 322 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 323 | 324 | const balance = web3.eth.getBalance(lottery.address).toNumber(); 325 | const ownerBalance = web3.eth.getBalance(charity).toNumber(); 326 | 327 | await lottery.setCharityAddress(charity, { from: owner }); 328 | await lottery.runLottery({ from: owner }); 329 | 330 | const newBalance = web3.eth.getBalance(charity).toNumber(); 331 | 332 | assert.ok(newBalance > ownerBalance); 333 | }); 334 | 335 | it('should initialise a new lottery', async() => { 336 | lottery = await KriptoLottery.new(5, web3.toWei(5, 'ether')); 337 | 338 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 339 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 340 | 341 | await lottery.setCharityAddress(charity, { from: owner }); 342 | await lottery.runLottery({ from: owner }); 343 | 344 | const currentLottery = await lottery.currentLottery.call(); 345 | 346 | assert.equal(2, currentLottery); 347 | }); 348 | 349 | it('should not send affiliate to affiliate account by default', async() => { 350 | lottery = await KriptoLottery.new(5, web3.toWei(5, 'ether')); 351 | 352 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 353 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 354 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 355 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 356 | 357 | await lottery.setCharityAddress(charity); 358 | await lottery.setAffiliateAddress(affiliate); 359 | 360 | const affiliateInitialBalance = web3.eth.getBalance(affiliate).toNumber(); 361 | 362 | await lottery.runLottery({ from: owner }); 363 | 364 | const newBalance = web3.eth.getBalance(affiliate).toNumber(); 365 | 366 | assert.ok(newBalance == affiliateInitialBalance); 367 | }); 368 | 369 | it('should send affiliate amount to affiliate account', async() => { 370 | lottery = await KriptoLottery.new(5, web3.toWei(5, 'ether')); 371 | 372 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 373 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 374 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 375 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 376 | 377 | await lottery.setCharityAddress(charity); 378 | await lottery.setAffiliateAddress(affiliate); 379 | await lottery.setDonationRatio(0); 380 | await lottery.setAffiliateRatio(90); 381 | 382 | const affiliateInitialBalance = web3.eth.getBalance(affiliate).toNumber(); 383 | 384 | 385 | await lottery.runLottery({ from: owner }); 386 | 387 | const newBalance = web3.eth.getBalance(affiliate).toNumber(); 388 | 389 | assert.ok(newBalance > affiliateInitialBalance); 390 | }); 391 | 392 | it('should update the total prize fields', async() => { 393 | lottery = await KriptoLottery.new(5, web3.toWei(5, 'ether')); 394 | 395 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 396 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 397 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 398 | await lottery.apply({ value: web3.toWei(5, 'ether') }); 399 | 400 | await lottery.setCharityAddress(charity); 401 | await lottery.setAffiliateAddress(affiliate); 402 | await lottery.setDonationRatio(30); 403 | await lottery.setAffiliateRatio(30); 404 | 405 | await lottery.runLottery({ from: owner }); 406 | 407 | const totalMoneyWon = await lottery.totalGivenAmount.call(); 408 | assert.ok(totalMoneyWon > 0); 409 | 410 | const totalDonation = await lottery.totalDonation.call(); 411 | assert.ok(totalDonation > 0); 412 | 413 | const totalAffiliateWon = await lottery.totalAffiliateAmount.call(); 414 | assert.ok(totalAffiliateWon > 0); 415 | }); 416 | }); 417 | 418 | describe('initialise new lottery', () => { 419 | it('should not run by non-owner', async() => { 420 | await lottery.pause({ from: owner }); 421 | try { 422 | await lottery.initialise({ from: someone }); 423 | } catch (error) { 424 | assert.ok(isRevertError(error)); 425 | return; 426 | } 427 | assert.fail('failed'); 428 | }); 429 | 430 | it('should not run unpaused', async() => { 431 | try { 432 | await lottery.initialise({ from: owner }); 433 | } catch (error) { 434 | assert.ok(isRevertError(error)); 435 | return; 436 | } 437 | assert.fail('failed'); 438 | }); 439 | 440 | it('should create a new lottery and increase the number', async() => { 441 | await lottery.pause(); 442 | 443 | await lottery.initialise(); 444 | 445 | const currentLottery = await lottery.currentLottery.call(); 446 | 447 | assert.equal(2, currentLottery); 448 | }); 449 | 450 | it('should not run if there is money left in the contract', async() => { 451 | await lottery.apply({ value: web3.toWei(0.02, 'ether') }); 452 | 453 | await lottery.pause(); 454 | 455 | try { 456 | await lottery.initialise(); 457 | } catch (error) { 458 | assert.ok(isRevertError(error)); 459 | return; 460 | } 461 | 462 | assert.fail('failed'); 463 | }); 464 | }); 465 | 466 | describe('set charity account', () => { 467 | it('should not run by non-owner', async() => { 468 | try { 469 | await lottery.setCharityAddress(charity, { from: someone }); 470 | } catch (error) { 471 | assert.ok(isRevertError(error)); 472 | return; 473 | } 474 | assert.fail('failed'); 475 | }); 476 | 477 | it('should not allow set empty address', async() => { 478 | try { 479 | await lottery.setCharityAddress(null, { from: owner }); 480 | } catch (error) { 481 | assert.ok(isRevertError(error)); 482 | return; 483 | } 484 | assert.fail('failed'); 485 | }); 486 | 487 | it('should set the new address', async() => { 488 | await lottery.setCharityAddress(charity, { from: owner }); 489 | 490 | const charityAddress = await lottery.charityAddress.call(); 491 | 492 | assert.equal(charity, charityAddress); 493 | }); 494 | 495 | // it('should set the new address', async() => { 496 | // await lottery.apply({ value: web3.toWei(0.02, 'ether') }); 497 | // await lottery.apply({ value: web3.toWei(0.02, 'ether') }); 498 | // await lottery.apply({ value: web3.toWei(0.02, 'ether') }); 499 | // await lottery.apply({ value: web3.toWei(0.02, 'ether') }); 500 | // await lottery.apply({ value: web3.toWei(0.02, 'ether') }); 501 | // await lottery.apply({ value: web3.toWei(0.02, 'ether') }); 502 | // await lottery.apply({ value: web3.toWei(0.02, 'ether') }); 503 | 504 | 505 | // const random = await lottery.runLottery(); 506 | 507 | // console.log(random[0], random[1]); 508 | // }); 509 | }); 510 | 511 | describe('setAffiliateRatio', () => { 512 | it('should only run by owner', async() => { 513 | try { 514 | await lottery.setAffiliateRatio(10, { from: someone }); 515 | } catch (error) { 516 | assert.equal(isRevertError(error), true); 517 | return; 518 | } 519 | assert.fail('failed'); 520 | }); 521 | 522 | it('should not be more than 100', async() => { 523 | try { 524 | await lottery.setAffiliateRatio(110, { from: owner }); 525 | } catch (error) { 526 | assert.equal(isRevertError(error), true); 527 | return; 528 | } 529 | assert.fail('failed'); 530 | }); 531 | 532 | it('should set the ratio', async() => { 533 | await lottery.setDonationRatio(10, { from: owner }); 534 | 535 | await lottery.setAffiliateRatio(10, { from: owner }); 536 | 537 | const ratio = await lottery.affiliateRatio.call(); 538 | 539 | assert(10, ratio); 540 | }); 541 | 542 | it('should be aware of total distribution ratio', async() => { 543 | await lottery.setDonationRatio(100, { from: owner }); 544 | try { 545 | await lottery.setAffiliateRatio(10, { from: owner }); 546 | } catch (error) { 547 | assert.equal(isRevertError(error), true); 548 | return; 549 | } 550 | assert.fail('failed'); 551 | }); 552 | }); 553 | 554 | describe('setDonationRatio', () => { 555 | it('should only run by owner', async() => { 556 | try { 557 | await lottery.setDonationRatio(10, { from: someone }); 558 | } catch (error) { 559 | assert.equal(isRevertError(error), true); 560 | return; 561 | } 562 | assert.fail('failed'); 563 | }); 564 | 565 | it('should set the ratio', async() => { 566 | await lottery.setDonationRatio(10, { from: owner }); 567 | 568 | const ratio = await lottery.donationRatio.call(); 569 | 570 | assert(10, ratio); 571 | }); 572 | 573 | it('should not be more than 100', async() => { 574 | try { 575 | await lottery.setDonationRatio(110, { from: owner }); 576 | } catch (error) { 577 | assert.equal(isRevertError(error), true); 578 | return; 579 | } 580 | assert.fail('failed'); 581 | }); 582 | 583 | it('should be aware of total distribution ratio', async() => { 584 | await lottery.setDonationRatio(0, { from: owner }); 585 | 586 | await lottery.setAffiliateRatio(100, { from: owner }); 587 | try { 588 | await lottery.setDonationRatio(10, { from: owner }); 589 | } catch (error) { 590 | assert.equal(isRevertError(error), true); 591 | return; 592 | } 593 | assert.fail('failed'); 594 | }); 595 | }); 596 | }); 597 | }); --------------------------------------------------------------------------------