From 27fb4f339ef5b9e97f5e173729e644fe16404713 Mon Sep 17 00:00:00 2001 From: Lyubomir Kiprov Date: Wed, 24 Apr 2019 13:56:18 +0300 Subject: [PATCH] Add Discover service implementation + some validators --- .eslintrc.json | 16 +++- config/blockchain.js | 86 +++++++++---------- package.json | 2 + .../discover-validator-utils.js | 14 +++ .../read-service/discover-r-service.js | 43 ++++++++++ .../read-service/validator.js | 26 ++++++ .../write-service/discover-w-service.js | 84 ++++++++++++++++++ .../write-service/validator.js | 71 +++++++++++++++ src/common/blockchain/index.js | 25 ++++++ src/common/utils/number-formatter.js | 29 +++++++ src/modules/App/Router.jsx | 2 + .../BlockchainExample.container.js | 4 + .../BlockchainExample/BlockchainExample.jsx | 17 ++++ src/modules/BlockchainExample/index.js | 3 + 14 files changed, 375 insertions(+), 47 deletions(-) create mode 100644 src/common/blockchain/discover-services/discover-validator-utils.js create mode 100644 src/common/blockchain/discover-services/read-service/discover-r-service.js create mode 100644 src/common/blockchain/discover-services/read-service/validator.js create mode 100644 src/common/blockchain/discover-services/write-service/discover-w-service.js create mode 100644 src/common/blockchain/discover-services/write-service/validator.js create mode 100644 src/common/blockchain/index.js create mode 100644 src/common/utils/number-formatter.js create mode 100644 src/modules/BlockchainExample/BlockchainExample.container.js create mode 100644 src/modules/BlockchainExample/BlockchainExample.jsx create mode 100644 src/modules/BlockchainExample/index.js diff --git a/.eslintrc.json b/.eslintrc.json index 402a1dc..b68da19 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,8 +1,16 @@ { - "extends": ["airbnb", "plugin:prettier/recommended"], - "plugins": ["prettier"], + "extends": [ + "airbnb", + "plugin:prettier/recommended" + ], + "plugins": [ + "prettier" + ], "rules": { - "prettier/prettier": "error" + "prettier/prettier": "error", + "func-names": "off", + "eqeqeq": "off", + "class-methods-use-this": "off" }, "env": { "browser": true, @@ -12,4 +20,4 @@ "parserOptions": { "ecmaVersion": 9 } -} +} \ No newline at end of file diff --git a/config/blockchain.js b/config/blockchain.js index a474d41..650bfaa 100644 --- a/config/blockchain.js +++ b/config/blockchain.js @@ -2,23 +2,23 @@ module.exports = { // applies to all environments default: { enabled: true, - rpcHost: "localhost", // HTTP-RPC server listening interface (default: "localhost") + rpcHost: 'localhost', // HTTP-RPC server listening interface (default: "localhost") rpcPort: 8545, // HTTP-RPC server listening port (default: 8545) rpcCorsDomain: { // Domains from which to accept cross origin requests (browser enforced). This can also be a comma separated list auto: true, // When "auto" is true, Embark will automatically set the cors to the address of the webserver - additionalCors: [] // Additional CORS domains to add to the list. If "auto" is false, only those will be added + additionalCors: [], // Additional CORS domains to add to the list. If "auto" is false, only those will be added }, wsRPC: true, // Enable the WS-RPC server wsOrigins: { // Same thing as "rpcCorsDomain", but for WS origins auto: true, - additionalCors: [] + additionalCors: [], }, - wsHost: "localhost", // WS-RPC server listening interface (default: "localhost") - wsPort: 8546 // WS-RPC server listening port (default: 8546) + wsHost: 'localhost', // WS-RPC server listening interface (default: "localhost") + wsPort: 8546, // WS-RPC server listening port (default: 8546) // Accounts to use as node accounts // The order here corresponds to the order of `web3.eth.getAccounts`, so the first one is the `defaultAccount` - /*,accounts: [ + /* ,accounts: [ { nodeAccounts: true, // Accounts use for the node numAddresses: "1", // Number of addresses/accounts (defaults to 1) @@ -39,68 +39,68 @@ module.exports = { numAddresses: "1", // Optional. The number of addresses to get hdpath: "m/44'/60'/0'/0/" // Optional. HD derivation path } - ]*/ + ] */ }, // default environment, merges with the settings in default // assumed to be the intended environment by `embark run` and `embark blockchain` development: { - ethereumClientName: "geth", // Can be geth or parity (default:geth) - //ethereumClientBin: "geth", // path to the client binary. Useful if it is not in the global PATH - networkType: "custom", // Can be: testnet, rinkeby, livenet or custom, in which case, it will use the specified networkId + ethereumClientName: 'geth', // Can be geth or parity (default:geth) + // ethereumClientBin: "geth", // path to the client binary. Useful if it is not in the global PATH + networkType: 'custom', // Can be: testnet, rinkeby, livenet or custom, in which case, it will use the specified networkId networkId: 1337, // Network id used when networkType is custom isDev: true, // Uses and ephemeral proof-of-authority network with a pre-funded developer account, mining enabled - datadir: ".embark/development/datadir", // Data directory for the databases and keystore (Geth 1.8.15 and Parity 2.0.4 can use the same base folder, till now they does not conflict with each other) + datadir: '.embark/development/datadir', // Data directory for the databases and keystore (Geth 1.8.15 and Parity 2.0.4 can use the same base folder, till now they does not conflict with each other) mineWhenNeeded: true, // Uses our custom script (if isDev is false) to mine only when needed nodiscover: true, // Disables the peer discovery mechanism (manual peer addition) maxpeers: 0, // Maximum number of network peers (network disabled if set to 0) (default: 25) proxy: true, // Proxy is used to present meaningful information about transactions targetGasLimit: 9000000, // Target gas limit sets the artificial target gas floor for the blocks to mine - simulatorBlocktime: 0 // Specify blockTime in seconds for automatic mining. Default is 0 and no auto-mining. + simulatorBlocktime: 0, // Specify blockTime in seconds for automatic mining. Default is 0 and no auto-mining. }, // merges with the settings in default // used with "embark run privatenet" and/or "embark blockchain privatenet" privatenet: { - networkType: "custom", + networkType: 'custom', networkId: 1337, isDev: false, - datadir: ".embark/privatenet/datadir", + datadir: '.embark/privatenet/datadir', // -- mineWhenNeeded -- - // This options is only valid when isDev is false. + // This options is only valid when isDev is false. // Enabling this option uses our custom script to mine only when needed. // Embark creates a development account for you (using `geth account new`) and funds the account. This account can be used for // development (and even imported in to MetaMask). To enable correct usage, a password for this account must be specified // in the `account > password` setting below. // NOTE: once `mineWhenNeeded` is enabled, you must run an `embark reset` on your dApp before running // `embark blockchain` or `embark run` for the first time. - mineWhenNeeded: true, + mineWhenNeeded: true, // -- genesisBlock -- // This option is only valid when mineWhenNeeded is true (which is only valid if isDev is false). // When enabled, geth uses POW to mine transactions as it would normally, instead of using POA as it does in --dev mode. - // On the first `embark blockchain or embark run` after this option is enabled, geth will create a new chain with a + // On the first `embark blockchain or embark run` after this option is enabled, geth will create a new chain with a // genesis block, which can be configured using the `genesisBlock` configuration option below. - genesisBlock: "config/privatenet/genesis.json", // Genesis block to initiate on first creation of a development node + genesisBlock: 'config/privatenet/genesis.json', // Genesis block to initiate on first creation of a development node nodiscover: true, maxpeers: 0, proxy: true, accounts: [ { nodeAccounts: true, - password: "config/privatenet/password" // Password to unlock the account - } + password: 'config/privatenet/password', // Password to unlock the account + }, ], targetGasLimit: 8000000, - simulatorBlocktime: 0 + simulatorBlocktime: 0, }, privateparitynet: { - ethereumClientName: "parity", - networkType: "custom", + ethereumClientName: 'parity', + networkType: 'custom', networkId: 1337, isDev: false, - genesisBlock: "config/privatenet/genesis-parity.json", // Genesis block to initiate on first creation of a development node - datadir: ".embark/privatenet/datadir", + genesisBlock: 'config/privatenet/genesis-parity.json', // Genesis block to initiate on first creation of a development node + datadir: '.embark/privatenet/datadir', mineWhenNeeded: false, nodiscover: true, maxpeers: 0, @@ -108,43 +108,43 @@ module.exports = { accounts: [ { nodeAccounts: true, - password: "config/privatenet/password" - } + password: 'config/privatenet/password', + }, ], targetGasLimit: 8000000, - simulatorBlocktime: 0 + simulatorBlocktime: 0, }, // merges with the settings in default // used with "embark run testnet" and/or "embark blockchain testnet" testnet: { - networkType: "testnet", - syncMode: "light", + networkType: 'testnet', + syncMode: 'light', accounts: [ { nodeAccounts: true, - password: "config/testnet/password" - } - ] + password: 'config/testnet/password', + }, + ], }, // merges with the settings in default // used with "embark run livenet" and/or "embark blockchain livenet" livenet: { - networkType: "livenet", - syncMode: "light", - rpcCorsDomain: "http://localhost:8000", - wsOrigins: "http://localhost:8000", + networkType: 'livenet', + syncMode: 'light', + rpcCorsDomain: 'http://localhost:8000', + wsOrigins: 'http://localhost:8000', accounts: [ { nodeAccounts: true, - password: "config/livenet/password" - } - ] - } + password: 'config/livenet/password', + }, + ], + }, // you can name an environment with specific settings and then specify with // "embark run custom_name" or "embark blockchain custom_name" - //custom_name: { - //} + // custom_name: { + // } }; diff --git a/package.json b/package.json index 7d21b62..5ec895b 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,14 @@ "version": "0.1.0", "private": true, "dependencies": { + "@babel/runtime-corejs2": "^7.4.3", "@trailofbits/embark-contract-info": "^1.0.0", "bignumber.js": "^8.1.1", "bs58": "^4.0.1", "connected-react-router": "^6.3.2", "debounce": "^1.2.0", "decimal.js": "^10.0.2", + "embark": "^4.0.2", "embark-solium": "0.0.1", "history": "^4.7.2", "moment": "^2.24.0", diff --git a/src/common/blockchain/discover-services/discover-validator-utils.js b/src/common/blockchain/discover-services/discover-validator-utils.js new file mode 100644 index 0000000..0274a40 --- /dev/null +++ b/src/common/blockchain/discover-services/discover-validator-utils.js @@ -0,0 +1,14 @@ +const discoverValidatorUtils = { + async checkDappCorrectness(dapp, id) { + if (dapp.id != id) { + throw new Error('Error fetching correct data') + } + }, + async checkUpVotingAmount(amount, limit) { + if (amount > limit) { + throw new Error('You cannot upvote by this much, try with a lower amount') + } + }, +} + +export default discoverValidatorUtils diff --git a/src/common/blockchain/discover-services/read-service/discover-r-service.js b/src/common/blockchain/discover-services/read-service/discover-r-service.js new file mode 100644 index 0000000..eedef19 --- /dev/null +++ b/src/common/blockchain/discover-services/read-service/discover-r-service.js @@ -0,0 +1,43 @@ +import DiscoverContract from '../../../../embarkArtifacts/contracts/Discover' +import DiscoverRServiceValidator from './validator' + +class DiscoverReadService { + constructor() { + this.validator = new DiscoverRServiceValidator(this) + } + + // TODO: Amount -> string/bigInt/number ? + // TODO: Maybe we can get id from a DApp name ? + // TODO: formatBigNumberToNumber + // TODO: validators - YUP + async upVoteEffect(id, amount) { + const dapp = await this.getDAppById(id) + await this.validator.validateUpVoteEffect(dapp, id, amount) + + return DiscoverContract.methods.upvoteEffect(id, amount).call() + } + + async downVoteCost(id) { + const dapp = await this.getDAppById(id) + await this.validator.validateDownVoteCost(dapp, id) + + return DiscoverContract.methods.upvoteEffect(id).call() + } + + async getDAppById(id) { + const dappId = await DiscoverContract.methods.id2index(id).call() + return DiscoverContract.methods.dapps(dappId).call() + } + + async safeMax() { + console.log(DiscoverContract) + debugger + return DiscoverContract.safeMax() + } + + async isDAppExists(id) { + return DiscoverContract.methods.existingIDs(id).call() + } +} + +export default DiscoverReadService diff --git a/src/common/blockchain/discover-services/read-service/validator.js b/src/common/blockchain/discover-services/read-service/validator.js new file mode 100644 index 0000000..bf04114 --- /dev/null +++ b/src/common/blockchain/discover-services/read-service/validator.js @@ -0,0 +1,26 @@ +import DiscoverValidatorUtils from '../discover-validator-utils' + +class DiscoverReadServiceValidator { + constructor(service) { + this.service = service + } + + async validateUpVoteEffect(id, amount) { + const dapp = await this.service.getDAppById() + await DiscoverValidatorUtils.checkDappCorrectness(dapp, id) + + // TODO: should check if dapp.balance is a big number + const safeMax = await this.service.safeMax() + await DiscoverValidatorUtils.checkUpVotingAmount( + dapp.balance + amount, + safeMax, + ) + } + + async validateDownVoteCost(id) { + const dapp = await this.service.getDAppById() + await DiscoverValidatorUtils.checkDappCorrectness(dapp, id) + } +} + +export default DiscoverReadServiceValidator diff --git a/src/common/blockchain/discover-services/write-service/discover-w-service.js b/src/common/blockchain/discover-services/write-service/discover-w-service.js new file mode 100644 index 0000000..b1d49ce --- /dev/null +++ b/src/common/blockchain/discover-services/write-service/discover-w-service.js @@ -0,0 +1,84 @@ +import DiscoverContract from '../../../../embarkArtifacts/contracts/Discover' +import DiscoverRService from '../read-service/discover-r-service' + +import DiscoverWServiceValidator from './validator' + +// TODO: Validators ? +// TODO: check for unlocked account: If it is not -> request unlocking +// TODO: preOperation -> inherited method ? +const unlockAccount = async function(account) { + return account +} + +class DiscoverWriteService extends DiscoverRService { + constructor(unlockedAccount) { + this.account = unlockedAccount + this.validator = new DiscoverWServiceValidator(this) + } + + async createDApp(id, amount, metadata) { + await unlockAccount(this.account) + await this.validator.validateDAppCreation(id, amount) + + try { + await DiscoverContract.methods.createDApp(id, amount, metadata, { + from: this.account, + }) + } catch (error) { + throw new Error('Transfer failed') + } + } + + async upVote(id, amount) { + await unlockAccount(this.account) + await this.validator.validateUpVoting(id, amount) + + try { + await DiscoverContract.methods.upVote(id, amount, { from: this.account }) + } catch (error) { + throw new Error('Transfer failed') + } + } + + async downVote(id, amount) { + await unlockAccount(this.account) + await this.validator.validateDownVoting(id, amount) + + try { + await DiscoverContract.methods.downVote(id, amount, { + from: this.account, + }) + } catch (error) { + throw new Error('Transfer failed') + } + } + + async withdraw(id, amount) { + await unlockAccount(this.account) + await this.validator.validateWithdrawing(id, amount) + + try { + await DiscoverContract.methods.withdraw(id, amount, this.account) + } catch (error) { + throw new Error('Transfer failed') + } + } + + async setMetadata(id, metadata) { + await unlockAccount(this.account) + await this.validator.validateMetadataSet(id) + + await DiscoverContract.methods.setMetadata(id, metadata, { + from: this.account, + }) + } + + // async receiveApproval(from, amount, token, data) { + // await unlockAccount(this.account); + // await this.validator.validateReceiveApproval(); + + // await DiscoverContract.methods.receiveApproval(from, amount, token, data, { from: this.account }); + // } +} + +export default DiscoverWriteService diff --git a/src/common/blockchain/discover-services/write-service/validator.js b/src/common/blockchain/discover-services/write-service/validator.js new file mode 100644 index 0000000..9587a27 --- /dev/null +++ b/src/common/blockchain/discover-services/write-service/validator.js @@ -0,0 +1,71 @@ +import DiscoverValidatorUtils from '../discover-validator-utils' +import DiscoverRServiceValidator from '../read-service/validator' + +class DiscoverWriteServiceValidator extends DiscoverRServiceValidator { + // TODO: Add SNT allowance checks + async validateDAppCreation(id, amount) { + const dappExists = await this.service.isDAppExists(id) + if (dappExists) { + throw new Error('You must submit a unique ID') + } + + if (amount <= 0) { + throw new Error( + 'You must spend some SNT to submit a ranking in order to avoid spam', + ) + } + + const safeMax = await this.service.safeMax() + if (amount > safeMax) { + throw new Error('You cannot stake more SNT than the ceiling dictates') + } + } + + // TODO: Add SNT allowance checks + async validateUpVoting(id, amount) { + await super.validateUpVoteEffect(id, amount) + + if (amount <= 0) { + throw new Error('You must send some SNT in order to upvote') + } + } + + // TODO: Add SNT allowance checks + async validateDownVoting(id, amount) { + await super.validateDownVoteCost(id) + + const downVoteCost = await this.service.downVoteCost(id) + if (downVoteCost != amount) { + throw new Error('Incorrect amount: valid iff effect on ranking is 1%') + } + } + + async validateWithdrawing(id, amount) { + const dapp = await this.service.getDAppById(id) + await DiscoverValidatorUtils.checkDappCorrectness(dapp, id) + + if (dapp.developer != this.service.account) { + throw new Error('Only the developer can withdraw SNT staked on this data') + } + + if (amount > dapp.available) { + throw new Error( + 'You can only withdraw a percentage of the SNT staked, less what you have already received', + ) + } + } + + async validateMetadataSet(id) { + const dapp = await this.service.getDAppById(id) + + if (dapp.developer != this.service.account) { + throw new Error('Only the developer can update the metadata') + } + } + + // async validateReceiveApproval() { + + // } +} + +export default DiscoverWriteServiceValidator diff --git a/src/common/blockchain/index.js b/src/common/blockchain/index.js new file mode 100644 index 0000000..71fead4 --- /dev/null +++ b/src/common/blockchain/index.js @@ -0,0 +1,25 @@ +// import DiscoverContract from '../../../embarkArtifacts/contracts/Discover'; +import EmbarkJS from '../../embarkArtifacts/embarkjs' + +import DiscoverReadService from './discover-services/read-service/discover-r-service' +import DiscoverWriteService from './discover-services/write-service/discover-w-service' + +const ReadOnlyServices = { + DiscoverService: new DiscoverReadService(), +} + +// TODO: ask Andy what kind of wallets is going to be used +const init = async function() { + try { + const account = await EmbarkJS.enableEthereum() + + const discoverService = new DiscoverWriteService(account) + + return { DiscoverService: discoverService, ...ReadOnlyServices } + } catch (error) { + // TODO: Should handle it in an elegant way + throw new Error(error.message) + } +} + +export default { init, ...ReadOnlyServices } diff --git a/src/common/utils/number-formatter.js b/src/common/utils/number-formatter.js new file mode 100644 index 0000000..043dabc --- /dev/null +++ b/src/common/utils/number-formatter.js @@ -0,0 +1,29 @@ +const ONE = '1000000000000000000' + +const formatBigNumberToNumber = function(bigNumber) { + let stringifyedNumber = bigNumber.toString(10) + + if (stringifyedNumber == '0') { + return stringifyedNumber + } + + let numberWholePartLength = 0 + + if (bigNumber.lt(ONE)) { + stringifyedNumber = stringifyedNumber.padStart(19, 0) + numberWholePartLength = 1 + } else { + numberWholePartLength = bigNumber.div('1000000000000000000').toString(10) + .length + } + + return `${stringifyedNumber.substr( + 0, + numberWholePartLength, + )}.${stringifyedNumber.substr( + numberWholePartLength, + stringifyedNumber.length, + )}` +} + +export default formatBigNumberToNumber diff --git a/src/modules/App/Router.jsx b/src/modules/App/Router.jsx index d1723bb..4480f03 100644 --- a/src/modules/App/Router.jsx +++ b/src/modules/App/Router.jsx @@ -5,6 +5,7 @@ import Filtered from '../Filtered' import RecentlyAdded from '../RecentlyAdded' import Vote from '../Vote' import Dapps from '../Dapps' +import Example from '../BlockchainExample' export default () => ( @@ -13,5 +14,6 @@ export default () => ( + ) diff --git a/src/modules/BlockchainExample/BlockchainExample.container.js b/src/modules/BlockchainExample/BlockchainExample.container.js new file mode 100644 index 0000000..bbae1ca --- /dev/null +++ b/src/modules/BlockchainExample/BlockchainExample.container.js @@ -0,0 +1,4 @@ +import { connect } from 'react-redux' +import BlockchainExample from './BlockchainExample' + +export default connect()(BlockchainExample) diff --git a/src/modules/BlockchainExample/BlockchainExample.jsx b/src/modules/BlockchainExample/BlockchainExample.jsx new file mode 100644 index 0000000..d4a61de --- /dev/null +++ b/src/modules/BlockchainExample/BlockchainExample.jsx @@ -0,0 +1,17 @@ +import React from 'react' +import BlockchainSDK from '../../common/blockchain' +// import EmbarkJS from '../../embarkArtifacts/embarkjs'; + +class Example extends React.Component { + async logDiscoverMethod() { + console.log('here') + debugger + console.log(await BlockchainSDK.DiscoverService.safeMax()) + } + + render() { + return

+ } +} + +export default Example diff --git a/src/modules/BlockchainExample/index.js b/src/modules/BlockchainExample/index.js new file mode 100644 index 0000000..1b16b3f --- /dev/null +++ b/src/modules/BlockchainExample/index.js @@ -0,0 +1,3 @@ +import BlockchainExample from './BlockchainExample.container' + +export default BlockchainExample