├── .idea ├── .name ├── encodings.xml ├── modules.xml ├── JediBet.iml └── misc.xml ├── migrations ├── 2_deploy_contracts.js └── 1_initial_migration.js ├── truffle.js ├── truffle-config.js ├── contracts ├── Migrations.sol └── Bet.sol ├── .gitignore ├── LICENSE ├── README.md └── test └── testBet.js /.idea/.name: -------------------------------------------------------------------------------- 1 | JediBet -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | var Bet = artifacts.require("./Bet.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Bet); 5 | }; -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // See 3 | // to customize your Truffle configuration! 4 | }; 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 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/JediBet.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .idea/* 61 | build/ 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /contracts/Bet.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.8; 2 | contract Bet { 3 | 4 | //jedi bet status 5 | uint constant STATUS_WIN = 1; 6 | uint constant STATUS_LOSE = 2; 7 | uint constant STATUS_TIE = 3; 8 | uint constant STATUS_PENDING = 4; 9 | 10 | //game status 11 | uint constant STATUS_NOT_STARTED = 1; 12 | uint constant STATUS_STARTED = 2; 13 | uint constant STATUS_COMPLETE = 3; 14 | 15 | //general status 16 | uint constant STATUS_ERROR = 4; 17 | 18 | //the 'better' structure 19 | struct JediBet { 20 | uint guess; 21 | address addr; 22 | uint status; 23 | } 24 | 25 | //the 'game' structure 26 | struct Game { 27 | uint256 betAmount; 28 | uint outcome; 29 | uint status; 30 | JediBet originator; 31 | JediBet taker; 32 | } 33 | 34 | //the game 35 | Game game; 36 | 37 | //fallback function 38 | function() public payable {} 39 | 40 | function createBet(uint _guess) public payable { 41 | game = Game(msg.value, 0, STATUS_STARTED, JediBet(_guess, msg.sender, STATUS_PENDING), JediBet(0, 0, STATUS_NOT_STARTED)); 42 | game.originator = JediBet(_guess, msg.sender, STATUS_PENDING); 43 | } 44 | 45 | function takeBet(uint _guess) public payable { 46 | //requires the taker to make the same bet amount 47 | require(msg.value == game.betAmount); 48 | game.taker = JediBet(_guess, msg.sender, STATUS_PENDING); 49 | generateBetOutcome(); 50 | } 51 | 52 | function payout() public payable { 53 | 54 | checkPermissions(msg.sender); 55 | 56 | if (game.originator.status == STATUS_TIE && game.taker.status == STATUS_TIE) { 57 | game.originator.addr.transfer(game.betAmount); 58 | game.taker.addr.transfer(game.betAmount); 59 | } else { 60 | if (game.originator.status == STATUS_WIN) { 61 | game.originator.addr.transfer(game.betAmount*2); 62 | } else if (game.taker.status == STATUS_WIN) { 63 | game.taker.addr.transfer(game.betAmount*2); 64 | } else { 65 | game.originator.addr.transfer(game.betAmount); 66 | game.taker.addr.transfer(game.betAmount); 67 | } 68 | } 69 | } 70 | 71 | function checkPermissions(address sender) view private { 72 | //only the originator or taker can call this function 73 | require(sender == game.originator.addr || sender == game.taker.addr); 74 | } 75 | 76 | function getBetAmount() public view returns (uint) { 77 | checkPermissions(msg.sender); 78 | return game.betAmount; 79 | } 80 | 81 | function getOriginatorGuess() public view returns (uint) { 82 | checkPermissions(msg.sender); 83 | return game.originator.guess; 84 | } 85 | 86 | function getTakerGuess() public view returns (uint) { 87 | checkPermissions(msg.sender); 88 | return game.taker.guess; 89 | } 90 | 91 | function getPot() public view returns (uint256) { 92 | checkPermissions(msg.sender); 93 | return this.balance; 94 | } 95 | 96 | function generateBetOutcome() private { 97 | //todo - not a great way to generate a random number but ok for now 98 | game.outcome = uint(block.blockhash(block.number-1))%10 + 1; 99 | game.status = STATUS_COMPLETE; 100 | 101 | if (game.originator.guess == game.taker.guess) { 102 | game.originator.status = STATUS_TIE; 103 | game.taker.status = STATUS_TIE; 104 | } else if (game.originator.guess > game.outcome && game.taker.guess > game.outcome) { 105 | game.originator.status = STATUS_TIE; 106 | game.taker.status = STATUS_TIE; 107 | } else { 108 | if ((game.outcome - game.originator.guess) < (game.outcome - game.taker.guess)) { 109 | game.originator.status = STATUS_WIN; 110 | game.taker.status = STATUS_LOSE; 111 | } else if ((game.outcome - game.taker.guess) < (game.outcome - game.originator.guess)) { 112 | game.originator.status = STATUS_LOSE; 113 | game.taker.status = STATUS_WIN; 114 | } else { 115 | game.originator.status = STATUS_ERROR; 116 | game.taker.status = STATUS_ERROR; 117 | game.status = STATUS_ERROR; 118 | } 119 | } 120 | } 121 | 122 | //returns - [, 'originator', , 'taker', ] 123 | function getBetOutcome() public view returns 124 | (string description, string originatorKey, uint originatorStatus, string takerKey, uint takerStatus) 125 | { 126 | if (game.originator.status == STATUS_TIE || game.taker.status == STATUS_TIE) { 127 | description = "Both bets were the same or were over the number, the pot will be split"; 128 | } else { 129 | if (game.originator.status == STATUS_WIN) { 130 | description = "Bet originator guess was closer to the number and will receive the pot"; 131 | } else if (game.taker.status == STATUS_WIN) { 132 | description = "Bet taker guess was closer to the number and will receive the pot"; 133 | } else { 134 | description = "Unknown Bet Outcome"; 135 | } 136 | } 137 | originatorKey = "originator"; 138 | originatorStatus = game.originator.status; 139 | takerKey = "taker"; 140 | takerStatus = game.taker.status; 141 | } 142 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Jedi’s Guide to Ethereum — Part 3 2 | 3 | Master Yoda and Obi-Wan love to gamble. If you can recall in [Part 4 | 1](https://medium.com/@cipherz/super-simple-ethereum-part-1-7e363dbc1c65) and 5 | [Part 2](https://medium.com/@cipherz/super-simple-ethereum-part-2-6611721a391) 6 | of this series, Yoda and Obi-Wan created a private Ethereum network in order to 7 | transfer some Eth between each other to settle a bet. They have now decided to 8 | create a smart contract to settle their bets in a trustless way. They want to do 9 | the following 10 | 11 | * Deploy a smart contract to hold the Eth and bets of each Jedi 12 | * Check the results of the bet 13 | * Use the deployed smart contract to pay out the winnings 14 | 15 | The Masters are into a really simple game called *over/under *— where they guess 16 | a number between 1 and 10. The rules are as follows 17 | 18 | * The number is **randomly** generated(by the smart contract in this case) 19 | * The player **closest** to the number without going over it wins 20 | * If the players guess the **same** number the winnings are split 21 | * If both players guess **above** the number the winnings are split 22 | * If there is an **error** in the contract execution, the bets are returned to the 23 | players 24 | 25 | #### Setup 26 | 27 | A contract is a bit of code that performs some logical operation based on given 28 | inputs. The contract is stored and executed in the blockchain. Ethereum’s 29 | preferred language for interacting with its blockchain is called *Solidity*. The 30 | syntax and usage of Solidity is beyond the scope of this article, but we will 31 | comment on the key parts of the code. Before we get into the code itself, let 32 | install a helpful development tool called, *truffle. *Truffle is a node.js CLI 33 | tool that will greatly simplify our Solidity development, testing, and 34 | deployment. 35 | 36 | Install *truffle* 37 | 38 | $ npm install -g truffle 39 | 40 | Create project 41 | 42 | $ mkdir ~/ethereum/JediBet && cd ~/ethereum/JediBet 43 | $ truffle init 44 | 45 | You should see the following structure 46 | 47 | $ ls 48 | build migrations truffle-config.js 49 | contracts test truffle.js 50 | 51 | Create a file named *Bet.sol *with the following content 52 | 53 | pragma solidity ^0.4.8; 54 | 55 | contract Bet { 56 | } 57 | 58 | Create a file named *2_deploy_contracts.js *with the following content 59 | 60 | Bet = artifacts.require("./Bet.sol"); 61 | module.exports = 62 | (deployer) { 63 | deployer.deploy(Bet); 64 | }; 65 | 66 | Run the *truffle *development environment to simulate an ethereum network. We 67 | will use our private network later, but this should be good enough to develop on 68 | right now. Notice how the ethereum testnet starts up with a command line as 69 | well. Pretty nice. 70 | 71 | $ truffle development 72 | ... 73 | truffle(develop)> 74 | 75 | Compile the empty contract, *Bet.sol* 76 | 77 | truffle(develop)> compile 78 | Compiling ./contracts/Bet.sol... 79 | Writing artifacts to ./build/contracts 80 | 81 | Migrate the contract(Publish it on the blockchain). Note that this publishes the 82 | contract to a local test network that truffle maintains. You can view the output 83 | from this network by running the following from the project directory. Also, 84 | note that this test network does not require you to mine the transactions as a 85 | part of your development and testing. In the real world you would have to mine 86 | any transaction or contract you send to the blockchain. 87 | 88 | truffle development --log 89 | 90 | truffle(develop)> migrate 91 | Compiling ./contracts/Bet.sol... 92 | Writing artifacts to ./build/contracts 93 | Using network 'develop'. 94 | Running migration: 1_initial_migration.js 95 | Replacing Migrations... 96 | ... 0x7bc67e72eca3ffc723a8279d2e00194a19d65f4f42d87b4c51b6e8a2245a5fe6 97 | Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0 98 | Saving successful migration to network... 99 | ... 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956 100 | Saving artifacts... 101 | Running migration: 2_deploy_contracts.js 102 | Replacing Bet... 103 | ... 0x5c1dfbe5827ed7380063290a48f2357ce2969f57ed59064a1c9222e66400ab56 104 | Bet: 0x345ca3e014aaf5dca488057592ee47305d9b3e10 105 | Saving successful migration to network... 106 | ... 0xf36163615f41ef7ed8f4a8f192149a0bf633fe1a2398ce001bf44c43dc7bdda0 107 | Saving artifacts... 108 | truffle(develop)> 109 | 110 | #### What? 111 | 112 | We have just published an empty ethereum contract, *Bet.sol*, to the truffle 113 | testnet. We can’t do anything with it because it has no logic in it yet, but we 114 | will add that in the next section. 115 | 116 | #### The Code 117 | 118 | Like good citizens of the Republic, we are creating tests for our code using 119 | truffle’s integration with the *Mocha *test framework. The *Empire *has been 120 | blocking online content through it’s galactic firewalls, but Yoda has managed to 121 | find some good Solidity resources through the decentralized hosting network, 122 | [Substratum](https://substratum.net/). Solidity code and tests are outside of 123 | the scope of this article but they are presented below for your reference. 124 | 125 | #### The Contract 126 | 127 | If you recall, the purpose of the contract is to facilitate the betting and 128 | payout of Yoda and Obi-Wan’s favorite betting game, ‘Over-Under’. It is 129 | basically guessing a number between 1 and 10 without going over it, but don’t 130 | tell them that. The code below supports the following functions 131 | 132 | * Originate Bet — The *originator* can create a bet with an amount and the value 133 | guessed 134 | * Take Bet — The *taker *can accept the bet by verifying the amount of the bet and 135 | passing in a value for their guess. 136 | * Payout Bet — Either gambler* *can request the contract to payout the bet upon 137 | its conclusion. 138 | 139 | A couple helpful points to consider while reviewing the contract 140 | 141 | * A *contract *can do pretty much everything an *account *can do(Receive, 142 | Transfer, Send, Transact). In this case, our gamblers are sending their ether to 143 | the smart contract where it is stored within the contract’s account. It is not 144 | an IOU, it is literally maintaining the pot for the gamblers and providing a 145 | trustless escrow for their outcome. 146 | * Any transaction with the Ethereum blockchain requires *gas *— and this is no 147 | exception. The truffle tests do not specify a gas amount, but the *gas* is being 148 | taken from the amount sent. Note that *gas* is paid by the **Sender **of a 149 | transaction, so the originator, taker, and contract all pay *gas* to make this 150 | trustless system work. Typically, the *gas *amounts are very small, however, it 151 | is up to the contract developer to make sure that the minimum amount of *gas* is 152 | used while still ensuring the safety of the contract. 153 | * Disclaimer — The contract below does not handle error conditions or prevent a 154 | bad actor from messing with our bet. We will improve our contract in the next 155 | article where we add a simple web ui to facilitate bets more easily. 156 | 157 | #### Bet.sol 158 | 159 | The contract above is testable with the mocha tests with truffle. Create a 160 | *testBet.js* file in the *test* root project directory. Use the content below 161 | 162 | #### testBet.js 163 | 164 | Run the test as follows 165 | 166 | truffle(develop)> test './test/testBet.js' 167 | Using network 'develop'. 168 | 169 | Contract: Bet 170 | ✓ We should be able to start a bet by setting a guess and sending the bet amount that the contract was initialized with (38ms) 171 | ✓ The originating bet amount in the contract should match the passed in values 172 | ✓ The originating bet guess in the contract should match the passed in values 173 | ✓ The originator balance should be less the bet amount and gas (160ms) 174 | ✓ We should be able to take a bet by setting a guess and sending the bet amount that the contract was initialized with 175 | ✓ Taking the bet should fail if the bet amount does not equal the bet amount that the contract was initialized with 176 | ✓ The taker bet guess in the contract should match the passed in values 177 | ✓ The taker balance should be less the bet amount and gas (142ms) 178 | ✓ The contract balance should reflect the originator and taker bets 179 | ✓ The taker or originator should be able to call the payout to transfer winnings 180 | ✓ Originator and Taker balances should reflect bet outcome (272ms) 181 | ✓ ONLY the taker or originator should be able to call the payout function 182 | ✓ ONLY the taker or originator should be able to call the getBetAmount function 183 | ✓ ONLY the taker or originator should be able to call the getOriginatorGuess function 184 | ✓ ONLY the taker or originator should be able to call the getTakerGuess function 185 | ✓ ONLY the taker or originator should be able to call the getPot function 186 | ✓ ONLY the taker or originator should be able to call the getBetAmount function 187 | 188 | 17 passing (929ms) 189 | 190 | truffle(develop)> 191 | 192 | #### Summary 193 | 194 | We learned a lot in this portion of our series 195 | 196 | * Setting up a development environment with truffle 197 | * Creating and deploying our first Solidity smart contract 198 | * Test-Driven-Development of our Solidity smart contract 199 | 200 | In our [next 201 | article](https://medium.com/@cipherz/a-jedis-guide-to-ethereum-part-3b-remix-1b8d98d909d4) 202 | we will deploy this smart contract to our local ethereum network using the Remix 203 | IDE and learn how to use this powerful resource. 204 | 205 | The updated code for this example is here: 206 | [https://github.com/cipherzzz/JediBet](https://github.com/cipherzzz/JediBet) 207 | 208 | * [Ethereum](https://medium.com/tag/ethereum?source=post) 209 | * [Solidity](https://medium.com/tag/solidity?source=post) 210 | * [Truffle](https://medium.com/tag/truffle?source=post) 211 | * [Smart Contra](https://medium.com/tag/smart-contra?source=post) 212 | 213 | ### [CipherZ](https://medium.com/@cipherz) 214 | -------------------------------------------------------------------------------- /test/testBet.js: -------------------------------------------------------------------------------- 1 | var Bet = artifacts.require("./contracts/Bet"); 2 | 3 | const betAmountInEth = 0.25; 4 | const wrongBetAmountInEth = 0.15; 5 | const agreedUponBetAmount = web3.toWei(betAmountInEth, "ether"); 6 | const wrongBetAmount = web3.toWei(wrongBetAmountInEth, "ether"); 7 | 8 | contract("Bet", function(accounts) { 9 | const betOriginator = accounts[0]; 10 | const betTaker = accounts[1]; 11 | const badActor = accounts[2]; 12 | const originatorBet = 4; 13 | const takerBet = 5; 14 | 15 | const originatorBalanceBeforeBet = web3.eth.getBalance(betOriginator); 16 | const takerBalanceBeforeBet = web3.eth.getBalance(betTaker); 17 | let originatorBalanceAfterBet; 18 | let takerBalanceAfterBet; 19 | 20 | it("We should be able to start a bet by setting a guess and sending the bet amount that the contract was initialized with", function() { 21 | return Bet.deployed().then(function(instance) { 22 | return instance.createBet 23 | .sendTransaction(originatorBet, { 24 | from: betOriginator, 25 | value: agreedUponBetAmount, 26 | }) 27 | .then(tx => { 28 | assert.notEqual(tx, "", "We should get a transaction hash"); 29 | }); 30 | }); 31 | }); 32 | 33 | it("The originating bet amount in the contract should match the passed in values", function() { 34 | return Bet.deployed().then(function(instance) { 35 | return instance 36 | .getBetAmount({ 37 | from: betOriginator, 38 | }) 39 | .then(betAmount => { 40 | //console.log("betAmount: " + agreedUponBetAmount); 41 | assert.equal(betAmount, agreedUponBetAmount, "Bet amounts don't match"); 42 | }); 43 | }); 44 | }); 45 | 46 | it("The originating bet guess in the contract should match the passed in values", function() { 47 | return Bet.deployed().then(function(instance) { 48 | return instance 49 | .getOriginatorGuess({ 50 | from: betOriginator, 51 | }) 52 | .then(betGuess => { 53 | assert.equal(betGuess, originatorBet, "Bet guesses don't match"); 54 | }); 55 | }); 56 | }); 57 | 58 | it("The originator balance should be less the bet amount and gas", function() { 59 | const originalBalanceMinusBet = originatorBalanceBeforeBet - agreedUponBetAmount; 60 | originatorBalanceAfterBet = web3.eth.getBalance(betOriginator); 61 | assert.equal( 62 | originatorBalanceAfterBet < originalBalanceMinusBet, 63 | true, 64 | "Current Balance should be less than original balance minus bet because of gas" 65 | ); 66 | }); 67 | 68 | it("We should be able to take a bet by setting a guess and sending the bet amount that the contract was initialized with", function() { 69 | return Bet.deployed().then(function(instance) { 70 | return instance.takeBet 71 | .sendTransaction(takerBet, { 72 | from: betTaker, 73 | value: agreedUponBetAmount, 74 | }) 75 | .then(tx => { 76 | assert.notEqual(tx, "", "We should get a transaction hash"); 77 | }); 78 | }); 79 | }); 80 | 81 | it("Taking the bet should fail if the bet amount does not equal the bet amount that the contract was initialized with", function() { 82 | return Bet.deployed().then(function(instance) { 83 | return instance.takeBet 84 | .sendTransaction(takerBet, { 85 | from: betTaker, 86 | value: wrongBetAmount, 87 | }) 88 | .catch(error => { 89 | assert.isDefined(error, "We should get an error"); 90 | }); 91 | }); 92 | }); 93 | 94 | it("The taker bet guess in the contract should match the passed in values", function() { 95 | return Bet.deployed().then(function(instance) { 96 | return instance 97 | .getTakerGuess({ 98 | from: betTaker, 99 | }) 100 | .then(betGuess => { 101 | assert.equal(betGuess, takerBet, "Bet guesses don't match"); 102 | }); 103 | }); 104 | }); 105 | 106 | it("The taker balance should be less the bet amount and gas", function() { 107 | const originalBalanceMinusBet = takerBalanceBeforeBet - agreedUponBetAmount; 108 | takerBalanceAfterBet = web3.eth.getBalance(betTaker); 109 | assert.equal( 110 | takerBalanceAfterBet < originalBalanceMinusBet, 111 | true, 112 | "Current Balance should be less than original balance minus bet because of gas" 113 | ); 114 | }); 115 | 116 | it("The contract balance should reflect the originator and taker bets", function() { 117 | return Bet.deployed().then(function(instance) { 118 | return instance 119 | .getPot({ 120 | from: betTaker, 121 | }) 122 | .then(balance => { 123 | assert.equal( 124 | balance.toString(), 125 | (agreedUponBetAmount * 2).toString(), 126 | "Contact Balance should equal the bet amounts " 127 | ); 128 | }); 129 | }); 130 | }); 131 | 132 | it("The taker or originator should be able to call the payout to transfer winnings", function() { 133 | return Bet.deployed().then(function(instance) { 134 | return instance 135 | .payout({ 136 | from: betTaker, 137 | }) 138 | .then(tx => { 139 | assert.notEqual(tx.tx, "", "We should get a transaction hash"); 140 | }); 141 | }); 142 | }); 143 | 144 | it("Originator and Taker balances should reflect bet outcome", function() { 145 | return Bet.deployed().then(function(instance) { 146 | return instance 147 | .getBetOutcome({ 148 | from: betTaker, 149 | }) 150 | .then(outcome => { 151 | assert.notEqual(outcome[0], "", "Bet outcome description should not be empty"); 152 | assert.notEqual(outcome[2], "", "Bet originator status should not be empty"); 153 | assert.notEqual(outcome[4], "", "Bet taker status should not be empty"); 154 | 155 | const originatorBalanceAfterPayout = web3.eth.getBalance(betOriginator); 156 | const takerBalanceAfterPayout = web3.eth.getBalance(betTaker); 157 | 158 | console.log(JSON.stringify(outcome)); 159 | 160 | if (outcome[2].toString() === "1") { 161 | let gain = originatorBalanceAfterPayout.minus(originatorBalanceBeforeBet); 162 | console.log("originator gain:" + gain); 163 | 164 | //if originator won 165 | assert.equal( 166 | gain.dividedBy(agreedUponBetAmount).greaterThan(0.9), 167 | true, 168 | "Balance Gain after payout for a winning bet should be within 10% of bet amount" 169 | ); 170 | } else if (outcome[4].toString() === "1") { 171 | let gain = takerBalanceAfterPayout.minus(takerBalanceBeforeBet); 172 | console.log("taker gain:" + gain); 173 | 174 | //if taker won 175 | assert.equal( 176 | gain.dividedBy(agreedUponBetAmount).greaterThan(0.9), 177 | true, 178 | "Balance Gain after payout for a winning bet should be within 10% of bet amount" 179 | ); 180 | } else { 181 | //a tie or error 182 | 183 | let takerDelta = takerBalanceBeforeBet.minus(takerBalanceAfterPayout).dividedBy(takerBalanceBeforeBet); 184 | 185 | let originatorDelta = originatorBalanceBeforeBet 186 | .minus(originatorBalanceAfterPayout) 187 | .dividedBy(originatorBalanceBeforeBet); 188 | 189 | console.log("originatorDelta: " + originatorDelta); 190 | console.log("takerDelta: " + takerDelta); 191 | 192 | assert.equal( 193 | takerDelta.lessThan(0.01) && originatorDelta.lessThan(0.01), 194 | true, 195 | "Balance after payout for a tied bet should be within 1% of original balance" 196 | ); 197 | } 198 | }); 199 | }); 200 | }); 201 | 202 | it("ONLY the taker or originator should be able to call the payout function", function() { 203 | return Bet.deployed().then(function(instance) { 204 | return instance 205 | .payout({ 206 | from: badActor, 207 | }) 208 | .catch(error => { 209 | assert.isDefined(error, "Only originator/taker can call function"); 210 | }); 211 | }); 212 | }); 213 | 214 | it("ONLY the taker or originator should be able to call the getBetAmount function", function() { 215 | return Bet.deployed().then(function(instance) { 216 | return instance 217 | .getBetAmount({ 218 | from: badActor, 219 | }) 220 | .catch(error => { 221 | assert.isDefined(error, "Only originator/taker can call function"); 222 | }); 223 | }); 224 | }); 225 | 226 | it("ONLY the taker or originator should be able to call the getOriginatorGuess function", function() { 227 | return Bet.deployed().then(function(instance) { 228 | return instance 229 | .getOriginatorGuess({ 230 | from: badActor, 231 | }) 232 | .catch(error => { 233 | assert.isDefined(error, "Only originator/taker can call function"); 234 | }); 235 | }); 236 | }); 237 | 238 | it("ONLY the taker or originator should be able to call the getTakerGuess function", function() { 239 | return Bet.deployed().then(function(instance) { 240 | return instance 241 | .getTakerGuess({ 242 | from: badActor, 243 | }) 244 | .catch(error => { 245 | assert.isDefined(error, "Only originator/taker can call function"); 246 | }); 247 | }); 248 | }); 249 | 250 | it("ONLY the taker or originator should be able to call the getPot function", function() { 251 | return Bet.deployed().then(function(instance) { 252 | return instance 253 | .getPot({ 254 | from: badActor, 255 | }) 256 | .catch(error => { 257 | assert.isDefined(error, "Only originator/taker can call function"); 258 | }); 259 | }); 260 | }); 261 | 262 | it("ONLY the taker or originator should be able to call the getBetAmount function", function() { 263 | return Bet.deployed().then(function(instance) { 264 | return instance 265 | .getBetAmount({ 266 | from: badActor, 267 | }) 268 | .catch(error => { 269 | assert.isDefined(error, "Only originator/taker can call function"); 270 | }); 271 | }); 272 | }); 273 | }); 274 | --------------------------------------------------------------------------------