github.com/muhammedhassanm/blockchain@v0.0.0-20200120143007-697261defd4d/blockapps-ba-master/server/lib/project/projectManager.js (about) 1 const ba = require('blockapps-rest'); 2 const rest = ba.rest; 3 const util = ba.common.util; 4 const config = ba.common.config; 5 const Promise = ba.common.Promise; 6 const BigNumber = ba.common.BigNumber; 7 const constants = ba.common.constants; 8 9 const contractName = 'ProjectManager'; 10 const contractFilename = `${config.libPath}/project/contracts/ProjectManager.sol`; 11 12 const ErrorCodes = rest.getEnums(`${config.libPath}/common/ErrorCodes.sol`).ErrorCodes; 13 const ProjectState = ba.rest.getEnums(`${config.libPath}/project/contracts/ProjectState.sol`).ProjectState; 14 const ProjectEvent = ba.rest.getEnums(`${config.libPath}/project/contracts/ProjectEvent.sol`).ProjectEvent; 15 const BidState = ba.rest.getEnums(`${config.libPath}/bid/contracts/BidState.sol`).BidState; 16 const projectContractName = require('./project').contractName; 17 18 function* uploadContract(admin, args) { 19 const contract = yield rest.uploadContract(admin, contractName, contractFilename, args); 20 yield compileSearch(contract); 21 contract.src = 'removed'; 22 return setContract(admin, contract); 23 } 24 25 function setContract(admin, contract) { 26 contract.getState = function* () { 27 return yield rest.getState(contract); 28 } 29 contract.createProject = function* (args) { 30 return yield createProject(admin, contract, args); 31 } 32 contract.getProject = function* (name) { 33 return yield getProject(admin, contract, name); 34 } 35 contract.getProjects = function* (name) { 36 return yield getProjects(contract); 37 } 38 contract.exists = function* (name) { 39 return yield exists(admin, contract, name); 40 } 41 contract.getProjectsByBuyer = function* (buyer) { 42 return yield getProjectsByBuyer(contract, buyer); 43 } 44 contract.getProjectsBySupplier = function* (supplier) { 45 return yield getProjectsBySupplier(contract, supplier); 46 } 47 contract.getProjectsByName = function* (name) { 48 return yield getProjectsByName(contract, name); 49 } 50 contract.getProjectsByState = function* (state) { 51 return yield getProjectsByState(contract, state); 52 } 53 contract.handleEvent = function* (name, projectEvent) { 54 return yield handleEvent(admin, contract, name, projectEvent); 55 } 56 contract.createBid = function* (name, supplier, amount) { 57 return yield createBid(admin, contract, name, supplier, amount); 58 } 59 contract.acceptBid = function* (buyer, bidId, name) { 60 return yield acceptBid(admin, contract, buyer, bidId, name); 61 } 62 contract.settleProject = function* (projectName, supplierAddress, bidAddress) { 63 return yield settleProject(admin, contract, projectName, supplierAddress, bidAddress); 64 } 65 contract.getAcceptedBid = getAcceptedBid; 66 67 return contract; 68 } 69 70 function* compileSearch(contract) { 71 rest.verbose('compileSearch', contract.codeHash); 72 if (yield rest.isSearchable(contract.codeHash)) { 73 return; 74 } 75 // compile dependencies 76 const bidJs = require('../bid/bid'); 77 const userJs = require('../user/user'); 78 const projectJs = require('./project'); 79 // compile 80 const searchable = [bidJs.contractName, projectJs.contractName, contractName]; 81 return yield rest.compileSearch(searchable, contractName, contractFilename); 82 } 83 84 // throws: ErrorCodes 85 // returns: record from search 86 function* createProject(admin, contract, args) { 87 rest.verbose('createProject', {admin, args}); 88 // function createProject( 89 // string name, 90 // string buyer, 91 // string description, 92 // string spec, 93 // uint price, 94 // uint created, 95 // uint targetDelivery 96 // ) returns (ErrorCodes) { 97 const method = 'createProject'; 98 99 const result = yield rest.callMethod(admin, contract, method, args); 100 const errorCode = parseInt(result[0]); 101 if (errorCode != ErrorCodes.SUCCESS) { 102 throw new Error(errorCode); 103 } 104 // get the contract data from search 105 const project = yield getProject(admin, contract, args.name); 106 return project; 107 } 108 109 // throws: ErrorCodes 110 // returns: record from search 111 function* createBid(admin, contract, name, supplier, amount) { 112 rest.verbose('createBid', {name, supplier, amount}); 113 // function createBid(string name, string supplier, uint amount) returns (ErrorCodes, uint) { 114 const method = 'createBid'; 115 const args = { 116 name: name, 117 supplier: supplier, 118 amount: amount, 119 }; 120 121 const result = yield rest.callMethod(admin, contract, method, args); 122 const errorCode = parseInt(result[0]); 123 if (errorCode != ErrorCodes.SUCCESS) { 124 throw new Error(errorCode); 125 } 126 const bidId = result[1]; 127 // block until the contract shows up in search 128 const bid = (yield rest.waitQuery(`Bid?id=eq.${bidId}`, 1))[0]; 129 return bid; 130 } 131 132 // throws: ErrorCodes 133 function* acceptBid(admin, contract, buyer, bidId, name) { // FIXME should go into the contract 134 rest.verbose('acceptBid', {admin, buyer, bidId, name}); 135 const bids = yield getBidsByName(name); 136 if (bids.length < 1) { 137 throw new Error(ErrorCodes.NOT_FOUND); 138 } 139 // find the winning bid 140 const winningBid = bids.filter(bid => { 141 return bid.id == bidId; 142 })[0]; 143 // accept the bid (will transfer funds from buyer to bid contract) 144 try { 145 yield setBidState(buyer, winningBid.address, BidState.ACCEPTED, winningBid.amount); 146 } catch(error) { 147 // check insufficient balance 148 console.log(error.status); 149 if (error.status == 400) { 150 throw new Error(ErrorCodes.INSUFFICIENT_BALANCE); 151 } 152 throw error; 153 } 154 // reject all other bids 155 for (let bid of bids) { 156 if (bid.id != bidId) { 157 yield setBidState(buyer, bid.address, BidState.REJECTED, 0); // REJECT 158 } 159 } 160 const result = yield handleEvent(admin, contract, name, ProjectEvent.ACCEPT); 161 return result; 162 } 163 164 function* setBidState(buyer, bidAddress, state, valueEther) { 165 rest.verbose('setBidState', {buyer, bidAddress, state, valueEther}); 166 const contract = { 167 name: 'Bid', 168 address: bidAddress, 169 } 170 171 // function setBidState(address bidAddress, BidState state) returns (ErrorCodes) { 172 const method = 'setBidState'; 173 const args = { 174 newState: state, 175 }; 176 // the api is expecting the buyers bloc-account address (not the app-user address) 177 const buyerAccount = { 178 name: buyer.username, 179 password: buyer.password, 180 address: buyer.account, 181 }; 182 183 const valueWei = new BigNumber(valueEther).mul(constants.ETHER); 184 const result = yield rest.callMethod(buyerAccount, contract, method, args, valueWei); 185 const errorCode = parseInt(result[0]); 186 if (errorCode != ErrorCodes.SUCCESS) { 187 throw new Error(errorCode); 188 } 189 } 190 191 function* settleProject(admin, contract, projectName, supplierAddress, bidAddress) { 192 rest.verbose('settleProject', {projectName, supplierAddress, bidAddress}); 193 // function settleProject(string name, address supplierAddress, address bidAddress) returns (ErrorCodes) { 194 const method = 'settleProject'; 195 const args = { 196 name: projectName, 197 supplierAddress: supplierAddress, 198 bidAddress: bidAddress, 199 }; 200 201 const result = yield rest.callMethod(admin, contract, method, args); 202 const errorCode = parseInt(result[0]); 203 if (errorCode != ErrorCodes.SUCCESS) { 204 throw new Error(errorCode); 205 } 206 } 207 208 function* getBid(bidId) { 209 rest.verbose('getBid', bidId); 210 return (yield rest.waitQuery(`Bid?id=eq.${bidId}`,1))[0]; 211 } 212 213 function* getBidsByName(name) { 214 rest.verbose('getBidsByName', name); 215 return yield rest.query(`Bid?name=eq.${encodeURIComponent(name)}`); 216 } 217 218 function* getBidsBySupplier(supplier) { 219 rest.verbose('getBidsBySupplier', supplier); 220 return yield rest.query(`Bid?supplier=eq.${supplier}`); 221 } 222 223 // throws: ErrorCodes 224 function* getAcceptedBid(projectName) { 225 rest.verbose('getAcceptedBid', projectName); 226 // get project bids 227 const bids = yield getBidsByName(projectName); 228 // extract the supplier out of the accepted bid 229 const accepted = bids.filter(bid => { 230 return parseInt(bid.state) === BidState.ACCEPTED; 231 }); 232 // not found 233 if (accepted.length == 0) { 234 throw new Error(ErrorCodes.NOT_FOUND); 235 } 236 // more then one 237 if (accepted.length > 1) { 238 throw new Error(ErrorCodes.ERROR); 239 } 240 return accepted[0]; 241 } 242 243 function* exists(admin, contract, name) { 244 rest.verbose('exists', name); 245 // function exists(string name) returns (bool) { 246 const method = 'exists'; 247 const args = { 248 name: name, 249 }; 250 const result = yield rest.callMethod(admin, contract, method, args); 251 const exists = (result[0] === true); 252 return exists; 253 } 254 255 function* getProject(admin, contract, name) { 256 rest.verbose('getProject', name); 257 // function getProject(string name) returns (address) { 258 const method = 'getProject'; 259 const args = { 260 name: name, 261 }; 262 263 // returns address 264 const address = (yield rest.callMethod(admin, contract, method, args))[0]; 265 // if not found - throw 266 if (address == 0) { 267 throw new Error(ErrorCodes.NOT_FOUND); 268 } 269 // found - query for the full record 270 const project = (yield rest.waitQuery(`${projectContractName}?address=eq.${address}`, 1))[0]; 271 return project; 272 } 273 274 function* getProjects(contract) { 275 rest.verbose('getProjects'); 276 const state = yield rest.getState(contract); 277 const projects = state.projects.slice(1); // remove the first '0000' project 278 const csv = util.toCsv(projects); // generate csv string 279 const results = yield rest.query(`${projectContractName}?address=in.${csv}`); 280 return results; 281 } 282 283 function* getProjectsByBuyer(contract, buyer) { 284 rest.verbose('getProjectsByBuyer', buyer); 285 const projects = yield getProjects(contract); 286 const filtered = projects.filter(function(project) { 287 return project.buyer === buyer; 288 }); 289 return filtered; 290 } 291 292 function* getProjectsByState(contract, state) { 293 rest.verbose('getProjectsByState', state); 294 const projects = yield getProjects(contract); 295 const filtered = projects.filter(function(project) { 296 return parseInt(project.state) == state; 297 }); 298 return filtered; 299 } 300 301 function* getProjectsBySupplier(contract, supplier) { 302 rest.verbose('getProjectsBySupplier', supplier); 303 const bids = yield getBidsBySupplier(supplier); 304 const names = bids.map(function(bid) { 305 return bid.name; 306 }); 307 const projects = yield getProjectsByName(contract, names); 308 return projects; 309 } 310 311 function* getProjectsByName(contract, names) { 312 rest.verbose('getProjectsByName', names); 313 if (names.length == 0) { 314 return []; 315 } 316 // the url might get too long, so the query is broken to multipart 317 const MAX = 50; // max names to list in one REST call 318 const parts = Math.ceil(names.length/MAX); 319 let results = []; 320 for (let i = 0; i < parts; i++) { 321 const start = i*MAX; 322 const end = (i<parts-1) ? (i+1)*MAX : names.length; 323 const csv = util.toCsv(names.slice(start, end)); // generate csv string 324 const partialResults = yield rest.query(`${projectContractName}?name=in.${encodeURIComponent(csv)}`); // get a part 325 results = results.concat(partialResults); // add to the results 326 } 327 return results; 328 } 329 330 function* handleEvent(admin, contract, name, projectEvent) { 331 rest.verbose('handleEvent', {admin, name, projectEvent}); 332 333 const project = yield getProject(admin, contract, name); 334 // function handleEvent(address projectAddress, ProjectEvent projectEvent) returns (ErrorCodes, ProjectState) { 335 const method = 'handleEvent'; 336 const args = { 337 projectAddress: project.address, 338 projectEvent: projectEvent, 339 }; 340 const result = yield rest.callMethod(admin, contract, method, args); 341 const errorCode = parseInt(result[0]); 342 if (errorCode != ErrorCodes.SUCCESS) { 343 throw new Error(errorCode); 344 } 345 const newState = parseInt(result[1]); 346 return newState; 347 } 348 349 module.exports = { 350 contractName:contractName, 351 compileSearch: compileSearch, 352 uploadContract: uploadContract, 353 setContract: setContract, 354 355 getAcceptedBid: getAcceptedBid, 356 getBid: getBid, 357 getBidsByName: getBidsByName, 358 getBidsBySupplier: getBidsBySupplier, 359 getProjectsByState: getProjectsByState, 360 };