diff --git a/ui/contracts/BlockApps.sol b/ui/contracts/BlockApps.sol new file mode 100644 index 0000000..47f95c1 --- /dev/null +++ b/ui/contracts/BlockApps.sol @@ -0,0 +1,432 @@ +contract ErrorCodes { + + enum ErrorCodes { + NULL, + SUCCESS, + ERROR, + NOT_FOUND, + EXISTS, + RECURSIVE, + INSUFFICIENT_BALANCE + } +} + +contract Version { + uint version; +} + +contract UserRole { + + enum UserRole { + NULL, + ADMIN, + BUYER, + SUPPLIER + } +} + +/** + * User data contract + */ +contract User is ErrorCodes, Version, UserRole { + // NOTE: members must be public to be indexed for search + address public account = 0x1234; + string public username; + bytes32 public pwHash; + uint public id; + UserRole public role; + + function User(address _account, string _username, bytes32 _pwHash, uint _id, UserRole _role) { + account = _account; + username = _username; + pwHash = _pwHash; + id = _id; + role = _role; + version = 1; + } + + function authenticate(bytes32 _pwHash) returns (bool) { + return pwHash == _pwHash; + } +} + +/** + * Util contract + */ +contract Util { + function stringToBytes32(string memory source) returns (bytes32 result) { + assembly { + result := mload(add(source, 32)) + } + } + + function b32(string memory source) returns (bytes32) { + return stringToBytes32(source); + } +} + +/** +* Interface for User data contracts +*/ +contract UserManager is ErrorCodes, Util, UserRole { + User[] users; + /* + note on mapping to array index: + a non existing mapping will return 0, so 0 should not be a valid value in a map, + otherwise exists() will not work + */ + mapping (bytes32 => uint) usernameToIdMap; + + /** + * Constructor + */ + function UserManager() { + users.length = 1; // see above note + } + + function exists(string username) returns (bool) { + return usernameToIdMap[b32(username)] != 0; + } + + function getUser(string username) returns (address) { + uint userId = usernameToIdMap[b32(username)]; + return users[userId]; + } + + function createUser(address account, string username, bytes32 pwHash, UserRole role) returns (ErrorCodes) { + // name must be < 32 bytes + if (bytes(username).length > 32) return ErrorCodes.ERROR; + // fail if username exists + if (exists(username)) return ErrorCodes.EXISTS; + // add user + uint userId = users.length; + usernameToIdMap[b32(username)] = userId; + users.push(new User(account, username, pwHash, userId, role)); + return ErrorCodes.SUCCESS; + } + + function login(string username, bytes32 pwHash) returns (bool) { + // fail if username doesnt exists + if (!exists(username)) return false; + // get the user + address a = getUser(username); + User user = User(a); + return user.authenticate(pwHash); + } +} + +contract ProjectState { + + enum ProjectState { + NULL, + OPEN, + PRODUCTION, + INTRANSIT, + RECEIVED + } +} + +/* + +{ + created: '2017-05-09T16:47:49.016Z', + buyer: 'buyer1', + name: 'T-Shirts with logo', + description: 'The T-Shirts with our company\'s logo on the chest, Qty: 50', + priceDesired: 800.10, + desiredDeliveryDate: '2017-05-20T16:47:49.016Z', + addressStreet: '109 S 5th street', + addresscity: 'Brooklyn', + addressstate: 'New York', + addresszip: '11249', + spec: 'Lorem ipsum dolor sit amet, eam molestie singulis referrentur', + state: 'OPEN', + deliveredDate: null // filled when the 'RECEIVED' button clicked + } +*/ + +/** + * Project data contract + */ +contract Project is ErrorCodes, ProjectState { + // NOTE: members must be public to be indexed for search + string public name; + string public buyer; + string public description; + string public spec; + uint public price; // in cents + + uint public created; // date + uint public targetDelivery; // date + uint public delivered; // date + + string public addressStreet; + string public addressCity; + string public addressState; + string public addressZip; + + ProjectState public state; + + function Project( + string _name, + string _buyer, + string _description, + string _spec, + uint _price, + uint _created, + uint _targetDelivery + ) { + name = _name; + buyer = _buyer; + description = _description; + spec = _spec; + price = _price; + created = _created; + targetDelivery = _targetDelivery; + + state = ProjectState.OPEN; + } + + function setShippingAddress( + string _addressStreet, + string _addressCity, + string _addressState, + string _addressZip + ) { + addressStreet = _addressStreet; + addressCity = _addressCity; + addressState = _addressState; + addressZip = _addressZip; + } + + function getState() returns (ProjectState) { + return state; + } + + function setState(ProjectState _state) { + state = _state; + } +} + +contract ProjectEvent { + + enum ProjectEvent { + NULL, + ACCEPT, + DELIVER, + RECEIVE + } +} + +contract BidState { + + enum BidState { + NULL, + OPEN, + ACCEPTED, + REJECTED + } +} + +/** + * Bid data contract + */ +contract Bid is ErrorCodes, BidState { + // NOTE: members must be public to be indexed for search + uint public id; + string public name; + string public supplier; + uint public amount; + BidState public state; + + function Bid(uint _id, string _name, string _supplier, uint _amount) { + id = _id; + name = _name; + supplier = _supplier; + amount = _amount; + state = BidState.OPEN; + } + + function getState() returns (BidState) { + return state; + } + + function setState(BidState _state) { + state = _state; + } + + function setBidState(BidState newState) payable returns (ErrorCodes) { + if (state == BidState.OPEN && newState == BidState.ACCEPTED) { + setState(newState); + return ErrorCodes.SUCCESS; + } + if (state == BidState.OPEN && newState == BidState.REJECTED) { + setState(newState); + return ErrorCodes.SUCCESS; + } + return ErrorCodes.ERROR; + } + + function settle(address supplierAddress) returns (ErrorCodes) { + // confirm balance, to return error + if (this.balance < amount) { + return ErrorCodes.INSUFFICIENT_BALANCE; + } + uint fee = 10000000 wei; // supplier absorbs the fee + uint amountWei = amount * 1 ether; + + // transfer will throw + supplierAddress.send(amountWei-fee); + return ErrorCodes.SUCCESS; + } +} + +/** +* Interface for Project data contracts +*/ +contract ProjectManager is ErrorCodes, Util, ProjectState, ProjectEvent, BidState { + + Project[] projects; + uint bidId; // unique identifier for bids + + /* + note on mapping to array index: + a non existing mapping will return 0, so 0 should not be a valid value in a map, + otherwise exists() will not work + */ + mapping (bytes32 => uint) nameToIndexMap; + + /** + * Constructor + */ + function ProjectManager() { + projects.length = 1; // see above note + bidId = block.number; + } + + function exists(string name) returns (bool) { + return nameToIndexMap[b32(name)] != 0; + } + + function getProject(string name) returns (address) { + uint index = nameToIndexMap[b32(name)]; + return projects[index]; + } + + /* + string addressStreet, + string addressCity, + string addressState, + string addressZip + */ + // addressStreet, + // addressCity, + // addressState, + // addressZip + + function createProject( + string name, + string buyer, + string description, + string spec, + uint price, + uint created, + uint targetDelivery + ) returns (ErrorCodes) { + // name must be < 32 bytes + if (bytes(name).length > 32) return ErrorCodes.ERROR; + // fail if username exists + if (exists(name)) return ErrorCodes.EXISTS; + // add project + uint index = projects.length; + nameToIndexMap[b32(name)] = index; + projects.push(new Project( + name, + buyer, + description, + spec, + price, + created, + targetDelivery + )); + return ErrorCodes.SUCCESS; + } + + function createBid(string name, string supplier, uint amount) returns (ErrorCodes, uint) { + // fail if project name not found + if (!exists(name)) return (ErrorCodes.NOT_FOUND, 0); + // create bid + bidId++; // increment the unique id + Bid bid = new Bid(bidId, name, supplier, amount); + return (ErrorCodes.SUCCESS, bidId); + } + + function settleProject(string name, address supplierAddress, address bidAddress) returns (ErrorCodes) { + // validity + if (!exists(name)) return (ErrorCodes.NOT_FOUND); + // set project state + address projectAddress = getProject(name); + var (errorCode, state) = handleEvent(projectAddress, ProjectEvent.RECEIVE); + if (errorCode != ErrorCodes.SUCCESS) return errorCode; + // settle + Bid bid = Bid(bidAddress); + return bid.settle(supplierAddress); + } + + /** + * handleEvent - transition project to a new state based on incoming event + */ + function handleEvent(address projectAddress, ProjectEvent projectEvent) returns (ErrorCodes, ProjectState) { + Project project = Project(projectAddress); + ProjectState state = project.getState(); + // check transition + var (errorCode, newState) = fsm(state, projectEvent); + // event is not valid in current state + if (errorCode != ErrorCodes.SUCCESS) { + return (errorCode, state); + } + // use the new state + project.setState(newState); + return (ErrorCodes.SUCCESS, newState); + } + + function fsm(ProjectState state, ProjectEvent projectEvent) returns (ErrorCodes, ProjectState) { + // NULL + if (state == ProjectState.NULL) + return (ErrorCodes.ERROR, state); + // OPEN + if (state == ProjectState.OPEN) { + if (projectEvent == ProjectEvent.ACCEPT) + return (ErrorCodes.SUCCESS, ProjectState.PRODUCTION); + } + // PRODUCTION + if (state == ProjectState.PRODUCTION) { + if (projectEvent == ProjectEvent.DELIVER) + return (ErrorCodes.SUCCESS, ProjectState.INTRANSIT); + } + // INTRANSIT + if (state == ProjectState.INTRANSIT) { + if (projectEvent == ProjectEvent.RECEIVE) + return (ErrorCodes.SUCCESS, ProjectState.RECEIVED); + } + return (ErrorCodes.ERROR, state); + } +} + + +/** + * Interface to global contracts +*/ +contract AdminInterface { + // NOTE: variable name must match contract name + UserManager public userManager; + ProjectManager public projectManager; + + /** + * Constructor. Initialize global contracts and pointers + */ + function AdminInterface() { + userManager = new UserManager(); + projectManager = new ProjectManager(); + } +} diff --git a/ui/initfile.json b/ui/initfile.json new file mode 100644 index 0000000..4edf23c --- /dev/null +++ b/ui/initfile.json @@ -0,0 +1,86 @@ +{ + "errorCodes": { + "contractName": "ErrorCodes", + "contractFilename": "contracts/BlockApps.sol", + "args": {} + }, + "version": { + "contractName": "Version", + "contractFilename": "contracts/BlockApps.sol", + "args": {} + }, + "userRole": { + "contractName": "UserRole", + "contractFilename": "contracts/BlockApps.sol", + "args": {} + }, + "user": { + "contractName": "User", + "contractFilename": "contracts/BlockApps.sol", + "args": { + "_account": "78fcb4b5c16e83d63866f0b82544cdf475710075", + "_username": "ABC", + "_pwHash": "qiyh4XPJGsOZ2MEAyLkfWqeQ", + "_id": "1000000000", + "_role": "ADMIN" + } + }, + "util": { + "contractName": "Util", + "contractFilename": "contracts/BlockApps.sol", + "args": {} + }, + "userManager": { + "contractName": "UserManager", + "contractFilename": "contracts/BlockApps.sol", + "args": {} + }, + "projectState": { + "contractName": "ProjectState", + "contractFilename": "contracts/BlockApps.sol", + "args": {} + }, + "project": { + "contractName": "Project", + "contractFilename": "contracts/BlockApps.sol", + "args": { + "_name": "ABC", + "_buyer": "XYZ", + "_description": "DEFG", + "_spec": "HIJ", + "_price": "1000", + "_created": "1281930090", + "_targetDelivery": "1681930090" + } + }, + "projectEvent": { + "contractName": "ProjectEvent", + "contractFilename": "contracts/BlockApps.sol", + "args": {} + }, + "bidState": { + "contractName": "BidState", + "contractFilename": "contracts/BlockApps.sol", + "args": {} + }, + "bid": { + "contractName": "Bid", + "contractFilename": "contracts/BlockApps.sol", + "args": { + "_id": "1000000000", + "_name": "ABC", + "_supplier": "XYZ", + "_amount": "1000" + } + }, + "projectManager": { + "contractName": "ProjectManager", + "contractFilename": "contracts/BlockApps.sol", + "args": {} + }, + "adminInterface": { + "contractName": "AdminInterface", + "contractFilename": "contracts/BlockApps.sol", + "args": {} + } +} \ No newline at end of file diff --git a/ui/package.json b/ui/package.json index f2517ca..db8425d 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,7 +1,9 @@ { "name": "ui", + "author": "BlockApps Inc", "version": "0.1.0", "private": true, + "homepage": ".", "dependencies": { "mixpanel-browser": "^2.11.1", "react": "15.5.4", @@ -22,7 +24,8 @@ "devDependencies": { "node-sass": "^4.5.2", "npm-run-all": "^4.0.2", - "react-scripts": "0.9.5" + "react-scripts": "0.9.5", + "read-package-json": "^2.0.13" }, "scripts": { "build-css": "node-sass src/ -o src/", @@ -32,8 +35,8 @@ "start:back": "npm-run-all -p watch-css start-js > stdout.text 2> stderr.txt &", "start-js-windows": "react-scripts start", "start-windows": "npm-run-all -p watch-css start-js-windows", - "build": "npm run build-css && react-scripts build", + "build": "npm run build-css && react-scripts build && cp initfile.json ./build && node scripts/create-metadata.js && cp -a contracts ./build && cd build && zip -r app.zip *", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" } -} \ No newline at end of file +} diff --git a/ui/public/index.html b/ui/public/index.html index 6cdc656..4280d28 100644 --- a/ui/public/index.html +++ b/ui/public/index.html @@ -6,6 +6,7 @@ +