├── .babelrc ├── .github └── workflows │ └── manual.yml ├── .gitignore ├── CODEOWNERS ├── LICENSE ├── README.md ├── config └── testConfig.js ├── contracts ├── FlightSuretyApp.sol ├── FlightSuretyData.sol └── Migrations.sol ├── migrations ├── 1_initial_migration.js └── 2_deploy_contracts.js ├── package.json ├── src ├── dapp │ ├── contract.js │ ├── dom.js │ ├── favicon.ico │ ├── flight.jpg │ ├── flightsurety.css │ ├── index.html │ └── index.js └── server │ ├── index.js │ └── server.js ├── test ├── flightSurety.js └── oracles.js ├── truffle.js ├── webpack.config.dapp.js └── webpack.config.server.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/env"], 3 | "plugins": [ 4 | "@babel/plugin-proposal-object-rest-spread", 5 | "@babel/plugin-proposal-class-properties" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.github/workflows/manual.yml: -------------------------------------------------------------------------------- 1 | # Workflow to ensure whenever a Github PR is submitted, 2 | # a JIRA ticket gets created automatically. 3 | name: Manual Workflow 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on pull request events but only for the master branch 8 | pull_request_target: 9 | types: [opened, reopened] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | jobs: 15 | test-transition-issue: 16 | name: Convert Github Issue to Jira Issue 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@master 21 | 22 | - name: Login 23 | uses: atlassian/gajira-login@master 24 | env: 25 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 26 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 27 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 28 | 29 | - name: Create NEW JIRA ticket 30 | id: create 31 | uses: atlassian/gajira-create@master 32 | with: 33 | project: CONUPDATE 34 | issuetype: Task 35 | summary: | 36 | Github PR ND1309 Blockchain C4 | Repo: ${{ github.repository }} | PR# ${{github.event.number}} 37 | description: | 38 | Repo link: https://github.com/${{ github.repository }} 39 | PR no. ${{ github.event.pull_request.number }} 40 | PR title: ${{ github.event.pull_request.title }} 41 | PR description: ${{ github.event.pull_request.description }} 42 | In addition, please resolve other issues, if any. 43 | fields: '{"components": [{"name":"Github PR"}], "customfield_16449":"https://classroom.udacity.com/", "customfield_16450":"Resolve the PR", "labels": ["github"], "priority":{"id": "4"}}' 44 | 45 | - name: Log created issue 46 | run: echo "Issue ${{ steps.create.outputs.issue }} was created" 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | src/dapp/config.json 8 | src/server/config.json 9 | /dapp/ 10 | prod/ 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (http://nodejs.org/api/addons.html) 37 | build/Release 38 | build/contracts 39 | build 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Typescript v1 declaration files 46 | typings/ 47 | 48 | # Optional npm cache directory 49 | .npm 50 | 51 | # Optional eslint cache 52 | .eslintcache 53 | 54 | # Optional REPL history 55 | .node_repl_history 56 | 57 | # Output of 'npm pack' 58 | *.tgz 59 | 60 | # Yarn Integrity file 61 | .yarn-integrity 62 | 63 | # dotenv environment variables file 64 | .env 65 | 66 | ~$*.pptx 67 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @udacity/active-public-content -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Udacity 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlightSurety 2 | 3 | FlightSurety is a sample application project for Udacity's Blockchain course. 4 | 5 | ## Install 6 | 7 | This repository contains Smart Contract code in Solidity (using Truffle), tests (also using Truffle), dApp scaffolding (using HTML, CSS and JS) and server app scaffolding. 8 | 9 | To install, download or clone the repo, then: 10 | 11 | `npm install` 12 | `truffle compile` 13 | 14 | ## Develop Client 15 | 16 | To run truffle tests: 17 | 18 | `truffle test ./test/flightSurety.js` 19 | `truffle test ./test/oracles.js` 20 | 21 | To use the dapp: 22 | 23 | `truffle migrate` 24 | `npm run dapp` 25 | 26 | To view dapp: 27 | 28 | `http://localhost:8000` 29 | 30 | ## Develop Server 31 | 32 | `npm run server` 33 | `truffle test ./test/oracles.js` 34 | 35 | ## Deploy 36 | 37 | To build dapp for prod: 38 | `npm run dapp:prod` 39 | 40 | Deploy the contents of the ./dapp folder 41 | 42 | 43 | ## Resources 44 | 45 | * [How does Ethereum work anyway?](https://medium.com/@preethikasireddy/how-does-ethereum-work-anyway-22d1df506369) 46 | * [BIP39 Mnemonic Generator](https://iancoleman.io/bip39/) 47 | * [Truffle Framework](http://truffleframework.com/) 48 | * [Ganache Local Blockchain](http://truffleframework.com/ganache/) 49 | * [Remix Solidity IDE](https://remix.ethereum.org/) 50 | * [Solidity Language Reference](http://solidity.readthedocs.io/en/v0.4.24/) 51 | * [Ethereum Blockchain Explorer](https://etherscan.io/) 52 | * [Web3Js Reference](https://github.com/ethereum/wiki/wiki/JavaScript-API) -------------------------------------------------------------------------------- /config/testConfig.js: -------------------------------------------------------------------------------- 1 | 2 | var FlightSuretyApp = artifacts.require("FlightSuretyApp"); 3 | var FlightSuretyData = artifacts.require("FlightSuretyData"); 4 | var BigNumber = require('bignumber.js'); 5 | 6 | var Config = async function(accounts) { 7 | 8 | // These test addresses are useful when you need to add 9 | // multiple users in test scripts 10 | let testAddresses = [ 11 | "0x69e1CB5cFcA8A311586e3406ed0301C06fb839a2", 12 | "0xF014343BDFFbED8660A9d8721deC985126f189F3", 13 | "0x0E79EDbD6A727CfeE09A2b1d0A59F7752d5bf7C9", 14 | "0x9bC1169Ca09555bf2721A5C9eC6D69c8073bfeB4", 15 | "0xa23eAEf02F9E0338EEcDa8Fdd0A73aDD781b2A86", 16 | "0x6b85cc8f612d5457d49775439335f83e12b8cfde", 17 | "0xcbd22ff1ded1423fbc24a7af2148745878800024", 18 | "0xc257274276a4e539741ca11b590b9447b26a8051", 19 | "0x2f2899d6d35b1a48a4fbdc93a37a72f264a9fca7" 20 | ]; 21 | 22 | 23 | let owner = accounts[0]; 24 | let firstAirline = accounts[1]; 25 | 26 | let flightSuretyData = await FlightSuretyData.new(); 27 | let flightSuretyApp = await FlightSuretyApp.new(); 28 | 29 | 30 | return { 31 | owner: owner, 32 | firstAirline: firstAirline, 33 | weiMultiple: (new BigNumber(10)).pow(18), 34 | testAddresses: testAddresses, 35 | flightSuretyData: flightSuretyData, 36 | flightSuretyApp: flightSuretyApp 37 | } 38 | } 39 | 40 | module.exports = { 41 | Config: Config 42 | }; -------------------------------------------------------------------------------- /contracts/FlightSuretyApp.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.25; 2 | 3 | // It's important to avoid vulnerabilities due to numeric overflow bugs 4 | // OpenZeppelin's SafeMath library, when used correctly, protects agains such bugs 5 | // More info: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2018/november/smart-contract-insecurity-bad-arithmetic/ 6 | 7 | import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol"; 8 | 9 | /************************************************** */ 10 | /* FlightSurety Smart Contract */ 11 | /************************************************** */ 12 | contract FlightSuretyApp { 13 | using SafeMath for uint256; // Allow SafeMath functions to be called for all uint256 types (similar to "prototype" in Javascript) 14 | 15 | /********************************************************************************************/ 16 | /* DATA VARIABLES */ 17 | /********************************************************************************************/ 18 | 19 | // Flight status codees 20 | uint8 private constant STATUS_CODE_UNKNOWN = 0; 21 | uint8 private constant STATUS_CODE_ON_TIME = 10; 22 | uint8 private constant STATUS_CODE_LATE_AIRLINE = 20; 23 | uint8 private constant STATUS_CODE_LATE_WEATHER = 30; 24 | uint8 private constant STATUS_CODE_LATE_TECHNICAL = 40; 25 | uint8 private constant STATUS_CODE_LATE_OTHER = 50; 26 | 27 | address private contractOwner; // Account used to deploy contract 28 | 29 | struct Flight { 30 | bool isRegistered; 31 | uint8 statusCode; 32 | uint256 updatedTimestamp; 33 | address airline; 34 | } 35 | mapping(bytes32 => Flight) private flights; 36 | 37 | 38 | /********************************************************************************************/ 39 | /* FUNCTION MODIFIERS */ 40 | /********************************************************************************************/ 41 | 42 | // Modifiers help avoid duplication of code. They are typically used to validate something 43 | // before a function is allowed to be executed. 44 | 45 | /** 46 | * @dev Modifier that requires the "operational" boolean variable to be "true" 47 | * This is used on all state changing functions to pause the contract in 48 | * the event there is an issue that needs to be fixed 49 | */ 50 | modifier requireIsOperational() 51 | { 52 | // Modify to call data contract's status 53 | require(true, "Contract is currently not operational"); 54 | _; // All modifiers require an "_" which indicates where the function body will be added 55 | } 56 | 57 | /** 58 | * @dev Modifier that requires the "ContractOwner" account to be the function caller 59 | */ 60 | modifier requireContractOwner() 61 | { 62 | require(msg.sender == contractOwner, "Caller is not contract owner"); 63 | _; 64 | } 65 | 66 | /********************************************************************************************/ 67 | /* CONSTRUCTOR */ 68 | /********************************************************************************************/ 69 | 70 | /** 71 | * @dev Contract constructor 72 | * 73 | */ 74 | constructor 75 | ( 76 | ) 77 | public 78 | { 79 | contractOwner = msg.sender; 80 | } 81 | 82 | /********************************************************************************************/ 83 | /* UTILITY FUNCTIONS */ 84 | /********************************************************************************************/ 85 | 86 | function isOperational() 87 | public 88 | pure 89 | returns(bool) 90 | { 91 | return true; // Modify to call data contract's status 92 | } 93 | 94 | /********************************************************************************************/ 95 | /* SMART CONTRACT FUNCTIONS */ 96 | /********************************************************************************************/ 97 | 98 | 99 | /** 100 | * @dev Add an airline to the registration queue 101 | * 102 | */ 103 | function registerAirline 104 | ( 105 | ) 106 | external 107 | pure 108 | returns(bool success, uint256 votes) 109 | { 110 | return (success, 0); 111 | } 112 | 113 | 114 | /** 115 | * @dev Register a future flight for insuring. 116 | * 117 | */ 118 | function registerFlight 119 | ( 120 | ) 121 | external 122 | pure 123 | { 124 | 125 | } 126 | 127 | /** 128 | * @dev Called after oracle has updated flight status 129 | * 130 | */ 131 | function processFlightStatus 132 | ( 133 | address airline, 134 | string memory flight, 135 | uint256 timestamp, 136 | uint8 statusCode 137 | ) 138 | internal 139 | pure 140 | { 141 | } 142 | 143 | 144 | // Generate a request for oracles to fetch flight information 145 | function fetchFlightStatus 146 | ( 147 | address airline, 148 | string flight, 149 | uint256 timestamp 150 | ) 151 | external 152 | { 153 | uint8 index = getRandomIndex(msg.sender); 154 | 155 | // Generate a unique key for storing the request 156 | bytes32 key = keccak256(abi.encodePacked(index, airline, flight, timestamp)); 157 | oracleResponses[key] = ResponseInfo({ 158 | requester: msg.sender, 159 | isOpen: true 160 | }); 161 | 162 | emit OracleRequest(index, airline, flight, timestamp); 163 | } 164 | 165 | 166 | // region ORACLE MANAGEMENT 167 | 168 | // Incremented to add pseudo-randomness at various points 169 | uint8 private nonce = 0; 170 | 171 | // Fee to be paid when registering oracle 172 | uint256 public constant REGISTRATION_FEE = 1 ether; 173 | 174 | // Number of oracles that must respond for valid status 175 | uint256 private constant MIN_RESPONSES = 3; 176 | 177 | 178 | struct Oracle { 179 | bool isRegistered; 180 | uint8[3] indexes; 181 | } 182 | 183 | // Track all registered oracles 184 | mapping(address => Oracle) private oracles; 185 | 186 | // Model for responses from oracles 187 | struct ResponseInfo { 188 | address requester; // Account that requested status 189 | bool isOpen; // If open, oracle responses are accepted 190 | mapping(uint8 => address[]) responses; // Mapping key is the status code reported 191 | // This lets us group responses and identify 192 | // the response that majority of the oracles 193 | } 194 | 195 | // Track all oracle responses 196 | // Key = hash(index, flight, timestamp) 197 | mapping(bytes32 => ResponseInfo) private oracleResponses; 198 | 199 | // Event fired each time an oracle submits a response 200 | event FlightStatusInfo(address airline, string flight, uint256 timestamp, uint8 status); 201 | 202 | event OracleReport(address airline, string flight, uint256 timestamp, uint8 status); 203 | 204 | // Event fired when flight status request is submitted 205 | // Oracles track this and if they have a matching index 206 | // they fetch data and submit a response 207 | event OracleRequest(uint8 index, address airline, string flight, uint256 timestamp); 208 | 209 | 210 | // Register an oracle with the contract 211 | function registerOracle 212 | ( 213 | ) 214 | external 215 | payable 216 | { 217 | // Require registration fee 218 | require(msg.value >= REGISTRATION_FEE, "Registration fee is required"); 219 | 220 | uint8[3] memory indexes = generateIndexes(msg.sender); 221 | 222 | oracles[msg.sender] = Oracle({ 223 | isRegistered: true, 224 | indexes: indexes 225 | }); 226 | } 227 | 228 | function getMyIndexes 229 | ( 230 | ) 231 | view 232 | external 233 | returns(uint8[3]) 234 | { 235 | require(oracles[msg.sender].isRegistered, "Not registered as an oracle"); 236 | 237 | return oracles[msg.sender].indexes; 238 | } 239 | 240 | 241 | 242 | 243 | // Called by oracle when a response is available to an outstanding request 244 | // For the response to be accepted, there must be a pending request that is open 245 | // and matches one of the three Indexes randomly assigned to the oracle at the 246 | // time of registration (i.e. uninvited oracles are not welcome) 247 | function submitOracleResponse 248 | ( 249 | uint8 index, 250 | address airline, 251 | string flight, 252 | uint256 timestamp, 253 | uint8 statusCode 254 | ) 255 | external 256 | { 257 | require((oracles[msg.sender].indexes[0] == index) || (oracles[msg.sender].indexes[1] == index) || (oracles[msg.sender].indexes[2] == index), "Index does not match oracle request"); 258 | 259 | 260 | bytes32 key = keccak256(abi.encodePacked(index, airline, flight, timestamp)); 261 | require(oracleResponses[key].isOpen, "Flight or timestamp do not match oracle request"); 262 | 263 | oracleResponses[key].responses[statusCode].push(msg.sender); 264 | 265 | // Information isn't considered verified until at least MIN_RESPONSES 266 | // oracles respond with the *** same *** information 267 | emit OracleReport(airline, flight, timestamp, statusCode); 268 | if (oracleResponses[key].responses[statusCode].length >= MIN_RESPONSES) { 269 | 270 | emit FlightStatusInfo(airline, flight, timestamp, statusCode); 271 | 272 | // Handle flight status as appropriate 273 | processFlightStatus(airline, flight, timestamp, statusCode); 274 | } 275 | } 276 | 277 | 278 | function getFlightKey 279 | ( 280 | address airline, 281 | string flight, 282 | uint256 timestamp 283 | ) 284 | pure 285 | internal 286 | returns(bytes32) 287 | { 288 | return keccak256(abi.encodePacked(airline, flight, timestamp)); 289 | } 290 | 291 | // Returns array of three non-duplicating integers from 0-9 292 | function generateIndexes 293 | ( 294 | address account 295 | ) 296 | internal 297 | returns(uint8[3]) 298 | { 299 | uint8[3] memory indexes; 300 | indexes[0] = getRandomIndex(account); 301 | 302 | indexes[1] = indexes[0]; 303 | while(indexes[1] == indexes[0]) { 304 | indexes[1] = getRandomIndex(account); 305 | } 306 | 307 | indexes[2] = indexes[1]; 308 | while((indexes[2] == indexes[0]) || (indexes[2] == indexes[1])) { 309 | indexes[2] = getRandomIndex(account); 310 | } 311 | 312 | return indexes; 313 | } 314 | 315 | // Returns array of three non-duplicating integers from 0-9 316 | function getRandomIndex 317 | ( 318 | address account 319 | ) 320 | internal 321 | returns (uint8) 322 | { 323 | uint8 maxValue = 10; 324 | 325 | // Pseudo random number...the incrementing nonce adds variation 326 | uint8 random = uint8(uint256(keccak256(abi.encodePacked(blockhash(block.number - nonce++), account))) % maxValue); 327 | 328 | if (nonce > 250) { 329 | nonce = 0; // Can only fetch blockhashes for last 256 blocks so we adapt 330 | } 331 | 332 | return random; 333 | } 334 | 335 | // endregion 336 | 337 | } 338 | -------------------------------------------------------------------------------- /contracts/FlightSuretyData.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.25; 2 | 3 | import "../node_modules/openzeppelin-solidity/contracts/math/SafeMath.sol"; 4 | 5 | contract FlightSuretyData { 6 | using SafeMath for uint256; 7 | 8 | /********************************************************************************************/ 9 | /* DATA VARIABLES */ 10 | /********************************************************************************************/ 11 | 12 | address private contractOwner; // Account used to deploy contract 13 | bool private operational = true; // Blocks all state changes throughout the contract if false 14 | 15 | /********************************************************************************************/ 16 | /* EVENT DEFINITIONS */ 17 | /********************************************************************************************/ 18 | 19 | 20 | /** 21 | * @dev Constructor 22 | * The deploying account becomes contractOwner 23 | */ 24 | constructor 25 | ( 26 | ) 27 | public 28 | { 29 | contractOwner = msg.sender; 30 | } 31 | 32 | /********************************************************************************************/ 33 | /* FUNCTION MODIFIERS */ 34 | /********************************************************************************************/ 35 | 36 | // Modifiers help avoid duplication of code. They are typically used to validate something 37 | // before a function is allowed to be executed. 38 | 39 | /** 40 | * @dev Modifier that requires the "operational" boolean variable to be "true" 41 | * This is used on all state changing functions to pause the contract in 42 | * the event there is an issue that needs to be fixed 43 | */ 44 | modifier requireIsOperational() 45 | { 46 | require(operational, "Contract is currently not operational"); 47 | _; // All modifiers require an "_" which indicates where the function body will be added 48 | } 49 | 50 | /** 51 | * @dev Modifier that requires the "ContractOwner" account to be the function caller 52 | */ 53 | modifier requireContractOwner() 54 | { 55 | require(msg.sender == contractOwner, "Caller is not contract owner"); 56 | _; 57 | } 58 | 59 | /********************************************************************************************/ 60 | /* UTILITY FUNCTIONS */ 61 | /********************************************************************************************/ 62 | 63 | /** 64 | * @dev Get operating status of contract 65 | * 66 | * @return A bool that is the current operating status 67 | */ 68 | function isOperational() 69 | public 70 | view 71 | returns(bool) 72 | { 73 | return operational; 74 | } 75 | 76 | 77 | /** 78 | * @dev Sets contract operations on/off 79 | * 80 | * When operational mode is disabled, all write transactions except for this one will fail 81 | */ 82 | function setOperatingStatus 83 | ( 84 | bool mode 85 | ) 86 | external 87 | requireContractOwner 88 | { 89 | operational = mode; 90 | } 91 | 92 | /********************************************************************************************/ 93 | /* SMART CONTRACT FUNCTIONS */ 94 | /********************************************************************************************/ 95 | 96 | /** 97 | * @dev Add an airline to the registration queue 98 | * Can only be called from FlightSuretyApp contract 99 | * 100 | */ 101 | function registerAirline 102 | ( 103 | ) 104 | external 105 | pure 106 | { 107 | } 108 | 109 | 110 | /** 111 | * @dev Buy insurance for a flight 112 | * 113 | */ 114 | function buy 115 | ( 116 | ) 117 | external 118 | payable 119 | { 120 | 121 | } 122 | 123 | /** 124 | * @dev Credits payouts to insurees 125 | */ 126 | function creditInsurees 127 | ( 128 | ) 129 | external 130 | pure 131 | { 132 | } 133 | 134 | 135 | /** 136 | * @dev Transfers eligible payout funds to insuree 137 | * 138 | */ 139 | function pay 140 | ( 141 | ) 142 | external 143 | pure 144 | { 145 | } 146 | 147 | /** 148 | * @dev Initial funding for the insurance. Unless there are too many delayed flights 149 | * resulting in insurance payouts, the contract should be self-sustaining 150 | * 151 | */ 152 | function fund 153 | ( 154 | ) 155 | public 156 | payable 157 | { 158 | } 159 | 160 | function getFlightKey 161 | ( 162 | address airline, 163 | string memory flight, 164 | uint256 timestamp 165 | ) 166 | pure 167 | internal 168 | returns(bytes32) 169 | { 170 | return keccak256(abi.encodePacked(airline, flight, timestamp)); 171 | } 172 | 173 | /** 174 | * @dev Fallback function for funding smart contract. 175 | * 176 | */ 177 | function() 178 | external 179 | payable 180 | { 181 | fund(); 182 | } 183 | 184 | 185 | } 186 | 187 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.25; 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 | constructor() 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 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_deploy_contracts.js: -------------------------------------------------------------------------------- 1 | const FlightSuretyApp = artifacts.require("FlightSuretyApp"); 2 | const FlightSuretyData = artifacts.require("FlightSuretyData"); 3 | const fs = require('fs'); 4 | 5 | module.exports = function(deployer) { 6 | 7 | let firstAirline = '0xf17f52151EbEF6C7334FAD080c5704D77216b732'; 8 | deployer.deploy(FlightSuretyData) 9 | .then(() => { 10 | return deployer.deploy(FlightSuretyApp) 11 | .then(() => { 12 | let config = { 13 | localhost: { 14 | url: 'http://localhost:8545', 15 | dataAddress: FlightSuretyData.address, 16 | appAddress: FlightSuretyApp.address 17 | } 18 | } 19 | fs.writeFileSync(__dirname + '/../src/dapp/config.json',JSON.stringify(config, null, '\t'), 'utf-8'); 20 | fs.writeFileSync(__dirname + '/../src/server/config.json',JSON.stringify(config, null, '\t'), 'utf-8'); 21 | }); 22 | }); 23 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flightsurety", 3 | "version": "1.0.0", 4 | "description": "", 5 | "directories": { 6 | "test": "test" 7 | }, 8 | "repository": "https://github.com/techbubble/flightsurety", 9 | "license": "MIT", 10 | "scripts": { 11 | "test": "truffle test ./test/flightSurety.js", 12 | "dapp": "webpack-dev-server --mode development --config webpack.config.dapp.js", 13 | "dapp:prod": "webpack --mode production --config webpack.config.dapp.js", 14 | "server": "rm -rf ./build/server && webpack --config webpack.config.server.js" 15 | }, 16 | "author": "Nik Kalyani https://www.kalyani.com", 17 | "devDependencies": { 18 | "@babel/cli": "^7.0.0-beta.46", 19 | "@babel/core": "^7.0.0-beta.46", 20 | "@babel/plugin-proposal-class-properties": "^7.0.0-beta.46", 21 | "@babel/plugin-proposal-object-rest-spread": "^7.0.0-beta.46", 22 | "@babel/preset-env": "^7.0.0-beta.46", 23 | "babel-core": "6.26.3", 24 | "babel-loader": "8.0.5", 25 | "babel-polyfill": "6.26.0", 26 | "babel-preset-es2015": "6.24.1", 27 | "babel-preset-stage-0": "6.24.1", 28 | "bignumber.js": "8.0.2", 29 | "css-loader": "^1.0.0", 30 | "express": "4.16.4", 31 | "file-loader": "3.0.1", 32 | "html-loader": "0.5.5", 33 | "html-webpack-plugin": "^3.2.0", 34 | "openzeppelin-solidity": "^1.10.0", 35 | "start-server-webpack-plugin": "2.2.5", 36 | "style-loader": "^0.23.1", 37 | "superstatic": "6.0.3", 38 | "truffle": "5.0.2", 39 | "truffle-hdwallet-provider": "1.0.2", 40 | "web3": "1.0.0-beta.37", 41 | "webpack": "^4.6.0", 42 | "webpack-cli": "^3.1.2", 43 | "webpack-dev-server": "3.1.14", 44 | "webpack-node-externals": "1.7.2" 45 | }, 46 | "dependencies": {} 47 | } 48 | -------------------------------------------------------------------------------- /src/dapp/contract.js: -------------------------------------------------------------------------------- 1 | import FlightSuretyApp from '../../build/contracts/FlightSuretyApp.json'; 2 | import Config from './config.json'; 3 | import Web3 from 'web3'; 4 | 5 | export default class Contract { 6 | constructor(network, callback) { 7 | 8 | let config = Config[network]; 9 | this.web3 = new Web3(new Web3.providers.HttpProvider(config.url)); 10 | this.flightSuretyApp = new this.web3.eth.Contract(FlightSuretyApp.abi, config.appAddress); 11 | this.initialize(callback); 12 | this.owner = null; 13 | this.airlines = []; 14 | this.passengers = []; 15 | } 16 | 17 | initialize(callback) { 18 | this.web3.eth.getAccounts((error, accts) => { 19 | 20 | this.owner = accts[0]; 21 | 22 | let counter = 1; 23 | 24 | while(this.airlines.length < 5) { 25 | this.airlines.push(accts[counter++]); 26 | } 27 | 28 | while(this.passengers.length < 5) { 29 | this.passengers.push(accts[counter++]); 30 | } 31 | 32 | callback(); 33 | }); 34 | } 35 | 36 | isOperational(callback) { 37 | let self = this; 38 | self.flightSuretyApp.methods 39 | .isOperational() 40 | .call({ from: self.owner}, callback); 41 | } 42 | 43 | fetchFlightStatus(flight, callback) { 44 | let self = this; 45 | let payload = { 46 | airline: self.airlines[0], 47 | flight: flight, 48 | timestamp: Math.floor(Date.now() / 1000) 49 | } 50 | self.flightSuretyApp.methods 51 | .fetchFlightStatus(payload.airline, payload.flight, payload.timestamp) 52 | .send({ from: self.owner}, (error, result) => { 53 | callback(error, payload); 54 | }); 55 | } 56 | } -------------------------------------------------------------------------------- /src/dapp/dom.js: -------------------------------------------------------------------------------- 1 | // Source: https://hackernoon.com/how-i-converted-my-react-app-to-vanillajs-and-whether-or-not-it-was-a-terrible-idea-4b14b1b2faff 2 | 3 | export default class DOM { 4 | 5 | static a = (...args) => DOM.makeElement(`a`, ...args); 6 | static button = (...args) => DOM.makeElement(`button`, ...args); 7 | static div = (...args) => DOM.makeElement(`div`, ...args); 8 | static h1 = (...args) => DOM.makeElement(`h1`, ...args); 9 | static h2 = (...args) => DOM.makeElement(`h2`, ...args); 10 | static h3 = (...args) => DOM.makeElement(`h3`, ...args); 11 | static h4 = (...args) => DOM.makeElement(`h4`, ...args); 12 | static h5 = (...args) => DOM.makeElement(`h5`, ...args); 13 | static header = (...args) => DOM.makeElement(`header`, ...args); 14 | static section = (...args) => DOM.makeElement(`section`, ...args); 15 | static p = (...args) => DOM.makeElement(`p`, ...args); 16 | static span = (...args) => DOM.makeElement(`span`, ...args); 17 | static img = (...args) => DOM.makeElement(`img`, ...args); 18 | static td = (...args) => DOM.makeElement(`td`, ...args); 19 | static attributeExceptions = [ 20 | `role`, 21 | ]; 22 | 23 | static elid(id) { 24 | return document.getElementById(id); 25 | } 26 | 27 | static appendText(el, text) { 28 | const textNode = document.createTextNode(text); 29 | el.appendChild(textNode); 30 | } 31 | 32 | static appendArray(el, children) { 33 | children.forEach((child) => { 34 | if (Array.isArray(child)) { 35 | DOM.appendArray(el, child); 36 | } else if (child instanceof window.Element) { 37 | el.appendChild(child); 38 | } else if (typeof child === `string`) { 39 | DOM.appendText(el, child); 40 | } 41 | }); 42 | } 43 | 44 | static setStyles(el, styles) { 45 | if (!styles) { 46 | el.removeAttribute(`styles`); 47 | return; 48 | } 49 | 50 | Object.keys(styles).forEach((styleName) => { 51 | if (styleName in el.style) { 52 | el.style[styleName] = styles[styleName]; // eslint-disable-line no-param-reassign 53 | } else { 54 | console.warn(`${styleName} is not a valid style for a <${el.tagName.toLowerCase()}>`); 55 | } 56 | }); 57 | } 58 | 59 | static makeElement(type, textOrPropsOrChild, ...otherChildren) { 60 | const el = document.createElement(type); 61 | 62 | if (Array.isArray(textOrPropsOrChild)) { 63 | DOM.appendArray(el, textOrPropsOrChild); 64 | } else if (textOrPropsOrChild instanceof window.Element) { 65 | el.appendChild(textOrPropsOrChild); 66 | } else if (typeof textOrPropsOrChild === `string`) { 67 | DOM.appendText(el, textOrPropsOrChild); 68 | } else if (typeof textOrPropsOrChild === `object`) { 69 | Object.keys(textOrPropsOrChild).forEach((propName) => { 70 | if (propName in el || attributeExceptions.includes(propName)) { 71 | const value = textOrPropsOrChild[propName]; 72 | 73 | if (propName === `style`) { 74 | DOM.setStyles(el, value); 75 | } else if (value) { 76 | el[propName] = value; 77 | } 78 | } else { 79 | console.warn(`${propName} is not a valid property of a <${type}>`); 80 | } 81 | }); 82 | } 83 | 84 | if (otherChildren) DOM.appendArray(el, otherChildren); 85 | 86 | return el; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/dapp/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/FlightSurety/93f5286dc2730cfa52adde0ac507bfd0952894ef/src/dapp/favicon.ico -------------------------------------------------------------------------------- /src/dapp/flight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/FlightSurety/93f5286dc2730cfa52adde0ac507bfd0952894ef/src/dapp/flight.jpg -------------------------------------------------------------------------------- /src/dapp/flightsurety.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-image: url('flight.jpg'); /* Source: iStockPhoto */ 3 | background-repeat: no-repeat; 4 | background-size: cover; 5 | } 6 | 7 | .container { 8 | margin-top: 400px; 9 | background-color: #000; 10 | opacity: 0.9; 11 | color: #fff; 12 | padding: 40px; 13 | } 14 | 15 | .top-20 { 16 | margin-top: 20px; 17 | } 18 | 19 | h5 { 20 | color: #999999; 21 | } 22 | 23 | section { 24 | margin-bottom: 50px; 25 | } 26 | 27 | .field { 28 | font-weight: bold; 29 | text-align: right; 30 | } 31 | 32 | .field-value { 33 | color: #0e7fa8; 34 | } 35 | 36 | label.form { 37 | font-size: 14px; 38 | margin-right: 20px; 39 | } 40 | input { 41 | margin-right: 30px; 42 | } -------------------------------------------------------------------------------- /src/dapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | FlightSurety 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 22 |
23 | 24 |
25 |
26 |
27 | Submit to Oracles 28 |
29 |
30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/dapp/index.js: -------------------------------------------------------------------------------- 1 | 2 | import DOM from './dom'; 3 | import Contract from './contract'; 4 | import './flightsurety.css'; 5 | 6 | 7 | (async() => { 8 | 9 | let result = null; 10 | 11 | let contract = new Contract('localhost', () => { 12 | 13 | // Read transaction 14 | contract.isOperational((error, result) => { 15 | console.log(error,result); 16 | display('Operational Status', 'Check if contract is operational', [ { label: 'Operational Status', error: error, value: result} ]); 17 | }); 18 | 19 | 20 | // User-submitted transaction 21 | DOM.elid('submit-oracle').addEventListener('click', () => { 22 | let flight = DOM.elid('flight-number').value; 23 | // Write transaction 24 | contract.fetchFlightStatus(flight, (error, result) => { 25 | display('Oracles', 'Trigger oracles', [ { label: 'Fetch Flight Status', error: error, value: result.flight + ' ' + result.timestamp} ]); 26 | }); 27 | }) 28 | 29 | }); 30 | 31 | 32 | })(); 33 | 34 | 35 | function display(title, description, results) { 36 | let displayDiv = DOM.elid("display-wrapper"); 37 | let section = DOM.section(); 38 | section.appendChild(DOM.h2(title)); 39 | section.appendChild(DOM.h5(description)); 40 | results.map((result) => { 41 | let row = section.appendChild(DOM.div({className:'row'})); 42 | row.appendChild(DOM.div({className: 'col-sm-4 field'}, result.label)); 43 | row.appendChild(DOM.div({className: 'col-sm-8 field-value'}, result.error ? String(result.error) : String(result.value))); 44 | section.appendChild(row); 45 | }) 46 | displayDiv.append(section); 47 | 48 | } 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/server/index.js: -------------------------------------------------------------------------------- 1 | 2 | import http from 'http' 3 | import app from './server' 4 | 5 | const server = http.createServer(app) 6 | let currentApp = app 7 | server.listen(3000) 8 | 9 | if (module.hot) { 10 | module.hot.accept('./server', () => { 11 | server.removeListener('request', currentApp) 12 | server.on('request', app) 13 | currentApp = app 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /src/server/server.js: -------------------------------------------------------------------------------- 1 | import FlightSuretyApp from '../../build/contracts/FlightSuretyApp.json'; 2 | import Config from './config.json'; 3 | import Web3 from 'web3'; 4 | import express from 'express'; 5 | 6 | 7 | let config = Config['localhost']; 8 | let web3 = new Web3(new Web3.providers.WebsocketProvider(config.url.replace('http', 'ws'))); 9 | web3.eth.defaultAccount = web3.eth.accounts[0]; 10 | let flightSuretyApp = new web3.eth.Contract(FlightSuretyApp.abi, config.appAddress); 11 | 12 | 13 | flightSuretyApp.events.OracleRequest({ 14 | fromBlock: 0 15 | }, function (error, event) { 16 | if (error) console.log(error) 17 | console.log(event) 18 | }); 19 | 20 | const app = express(); 21 | app.get('/api', (req, res) => { 22 | res.send({ 23 | message: 'An API for use with your Dapp!' 24 | }) 25 | }) 26 | 27 | export default app; 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/flightSurety.js: -------------------------------------------------------------------------------- 1 | 2 | var Test = require('../config/testConfig.js'); 3 | var BigNumber = require('bignumber.js'); 4 | 5 | contract('Flight Surety Tests', async (accounts) => { 6 | 7 | var config; 8 | before('setup contract', async () => { 9 | config = await Test.Config(accounts); 10 | await config.flightSuretyData.authorizeCaller(config.flightSuretyApp.address); 11 | }); 12 | 13 | /****************************************************************************************/ 14 | /* Operations and Settings */ 15 | /****************************************************************************************/ 16 | 17 | it(`(multiparty) has correct initial isOperational() value`, async function () { 18 | 19 | // Get operating status 20 | let status = await config.flightSuretyData.isOperational.call(); 21 | assert.equal(status, true, "Incorrect initial operating status value"); 22 | 23 | }); 24 | 25 | it(`(multiparty) can block access to setOperatingStatus() for non-Contract Owner account`, async function () { 26 | 27 | // Ensure that access is denied for non-Contract Owner account 28 | let accessDenied = false; 29 | try 30 | { 31 | await config.flightSuretyData.setOperatingStatus(false, { from: config.testAddresses[2] }); 32 | } 33 | catch(e) { 34 | accessDenied = true; 35 | } 36 | assert.equal(accessDenied, true, "Access not restricted to Contract Owner"); 37 | 38 | }); 39 | 40 | it(`(multiparty) can allow access to setOperatingStatus() for Contract Owner account`, async function () { 41 | 42 | // Ensure that access is allowed for Contract Owner account 43 | let accessDenied = false; 44 | try 45 | { 46 | await config.flightSuretyData.setOperatingStatus(false); 47 | } 48 | catch(e) { 49 | accessDenied = true; 50 | } 51 | assert.equal(accessDenied, false, "Access not restricted to Contract Owner"); 52 | 53 | }); 54 | 55 | it(`(multiparty) can block access to functions using requireIsOperational when operating status is false`, async function () { 56 | 57 | await config.flightSuretyData.setOperatingStatus(false); 58 | 59 | let reverted = false; 60 | try 61 | { 62 | await config.flightSurety.setTestingMode(true); 63 | } 64 | catch(e) { 65 | reverted = true; 66 | } 67 | assert.equal(reverted, true, "Access not blocked for requireIsOperational"); 68 | 69 | // Set it back for other tests to work 70 | await config.flightSuretyData.setOperatingStatus(true); 71 | 72 | }); 73 | 74 | it('(airline) cannot register an Airline using registerAirline() if it is not funded', async () => { 75 | 76 | // ARRANGE 77 | let newAirline = accounts[2]; 78 | 79 | // ACT 80 | try { 81 | await config.flightSuretyApp.registerAirline(newAirline, {from: config.firstAirline}); 82 | } 83 | catch(e) { 84 | 85 | } 86 | let result = await config.flightSuretyData.isAirline.call(newAirline); 87 | 88 | // ASSERT 89 | assert.equal(result, false, "Airline should not be able to register another airline if it hasn't provided funding"); 90 | 91 | }); 92 | 93 | 94 | }); 95 | -------------------------------------------------------------------------------- /test/oracles.js: -------------------------------------------------------------------------------- 1 | 2 | var Test = require('../config/testConfig.js'); 3 | //var BigNumber = require('bignumber.js'); 4 | 5 | contract('Oracles', async (accounts) => { 6 | 7 | const TEST_ORACLES_COUNT = 20; 8 | var config; 9 | before('setup contract', async () => { 10 | config = await Test.Config(accounts); 11 | 12 | // Watch contract events 13 | const STATUS_CODE_UNKNOWN = 0; 14 | const STATUS_CODE_ON_TIME = 10; 15 | const STATUS_CODE_LATE_AIRLINE = 20; 16 | const STATUS_CODE_LATE_WEATHER = 30; 17 | const STATUS_CODE_LATE_TECHNICAL = 40; 18 | const STATUS_CODE_LATE_OTHER = 50; 19 | 20 | }); 21 | 22 | 23 | it('can register oracles', async () => { 24 | 25 | // ARRANGE 26 | let fee = await config.flightSuretyApp.REGISTRATION_FEE.call(); 27 | 28 | // ACT 29 | for(let a=1; a { 37 | 38 | // ARRANGE 39 | let flight = 'ND1309'; // Course number 40 | let timestamp = Math.floor(Date.now() / 1000); 41 | 42 | // Submit a request for oracles to get status information for a flight 43 | await config.flightSuretyApp.fetchFlightStatus(config.firstAirline, flight, timestamp); 44 | // ACT 45 | 46 | // Since the Index assigned to each test account is opaque by design 47 | // loop through all the accounts and for each account, all its Indexes (indices?) 48 | // and submit a response. The contract will reject a submission if it was 49 | // not requested so while sub-optimal, it's a good test of that feature 50 | for(let a=1; a