mirror of
https://github.com/status-im/discover-dapps.git
synced 2025-03-03 19:40:50 +00:00
Merge branch 'develop' of https://github.com/bakasura980/discover-dapps into develop
This commit is contained in:
commit
b566c248bf
@ -1,10 +1,21 @@
|
||||
{
|
||||
"extends": ["airbnb", "plugin:prettier/recommended"],
|
||||
"plugins": ["prettier"],
|
||||
"extends": [
|
||||
"airbnb",
|
||||
"plugin:prettier/recommended"
|
||||
],
|
||||
"plugins": [
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": ["error", {
|
||||
"endOfLine":"auto"
|
||||
}]
|
||||
"prettier/prettier": [
|
||||
"error",
|
||||
{
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
],
|
||||
"func-names": "off",
|
||||
"eqeqeq": "off",
|
||||
"class-methods-use-this": "off"
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
@ -14,4 +25,4 @@
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 9
|
||||
}
|
||||
}
|
||||
}
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
.embark
|
||||
chains.json
|
||||
config/development/mnemonic
|
||||
config/livenet/password
|
||||
config/production/password
|
||||
coverage
|
||||
|
@ -2,105 +2,104 @@ 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
|
||||
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
|
||||
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: [
|
||||
{
|
||||
nodeAccounts: true, // Accounts use for the node
|
||||
numAddresses: "1", // Number of addresses/accounts (defaults to 1)
|
||||
password: "config/development/devpassword" // Password file for the accounts
|
||||
},
|
||||
// Below are additional accounts that will count as `nodeAccounts` in the `deployment` section of your contract config
|
||||
// Those will not be unlocked in the node itself
|
||||
{
|
||||
privateKey: "your_private_key"
|
||||
},
|
||||
{
|
||||
privateKeyFile: "path/to/file", // Either a keystore or a list of keys, separated by , or ;
|
||||
password: "passwordForTheKeystore" // Needed to decrypt the keystore file
|
||||
},
|
||||
{
|
||||
mnemonic: "12 word mnemonic",
|
||||
addressIndex: "0", // Optional. The index to start getting the address
|
||||
numAddresses: "1", // Optional. The number of addresses to get
|
||||
hdpath: "m/44'/60'/0'/0/" // Optional. HD derivation path
|
||||
}
|
||||
]*/
|
||||
// accounts: [
|
||||
// {
|
||||
// nodeAccounts: true, // Accounts use for the node
|
||||
// numAddresses: '1', // Number of addresses/accounts (defaults to 1)
|
||||
// password: 'config/development/password', // Password file for the accounts
|
||||
// },
|
||||
// Below are additional accounts that will count as `nodeAccounts` in the `deployment` section of your contract config
|
||||
// Those will not be unlocked in the node itself
|
||||
// {
|
||||
// privateKeyFile: 'path/to/file', // Either a keystore or a list of keys, separated by , or ;
|
||||
// password: 'passwordForTheKeystore', // Needed to decrypt the keystore file
|
||||
// },
|
||||
// {
|
||||
// mnemonic: '12 word mnemonic',
|
||||
// addressIndex: '0', // Optional. The index to start getting the address
|
||||
// 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 +107,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: {
|
||||
// }
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
const wallet = require('./development/mnemonic')
|
||||
|
||||
module.exports = {
|
||||
// default applies to all environments
|
||||
default: {
|
||||
// Blockchain node to deploy the contracts
|
||||
deployment: {
|
||||
host: "localhost", // Host of the blockchain node
|
||||
port: 8546, // Port of the blockchain node
|
||||
type: "ws" // Type of connection (ws or rpc),
|
||||
host: 'localhost', // Host of the blockchain node
|
||||
port: 8545, // Port of the blockchain node
|
||||
type: 'rpc', // Type of connection (ws or rpc),
|
||||
// Accounts to use instead of the default account to populate your wallet
|
||||
// The order here corresponds to the order of `web3.eth.getAccounts`, so the first one is the `defaultAccount`
|
||||
/*,accounts: [
|
||||
/* ,accounts: [
|
||||
{
|
||||
privateKey: "your_private_key",
|
||||
balance: "5 ether" // You can set the balance of the account in the dev environment
|
||||
@ -27,13 +29,20 @@ module.exports = {
|
||||
{
|
||||
"nodeAccounts": true // Uses the Ethereum node's accounts
|
||||
}
|
||||
]*/
|
||||
] */
|
||||
|
||||
accounts: [
|
||||
{
|
||||
mnemonic: wallet.mnemonic,
|
||||
balance: '1534983463450 ether',
|
||||
},
|
||||
],
|
||||
},
|
||||
// order of connections the dapp should connect to
|
||||
dappConnection: [
|
||||
"$WEB3", // uses pre existing web3 object if available (e.g in Mist)
|
||||
"ws://localhost:8546",
|
||||
"http://localhost:8545"
|
||||
'$WEB3', // uses pre existing web3 object if available (e.g in Mist)
|
||||
'ws://localhost:8546',
|
||||
'http://localhost:8545',
|
||||
],
|
||||
|
||||
// Automatically call `ethereum.enable` if true.
|
||||
@ -41,7 +50,7 @@ module.exports = {
|
||||
// Default value is true.
|
||||
// dappAutoEnable: true,
|
||||
|
||||
gas: "auto",
|
||||
gas: 'auto',
|
||||
|
||||
// Strategy for the deployment of the contracts:
|
||||
// - implicit will try to deploy all the contracts located inside the contracts directory
|
||||
@ -49,44 +58,62 @@ module.exports = {
|
||||
// when not specified
|
||||
// - explicit will only attempt to deploy the contracts that are explicitly specified inside the
|
||||
// contracts section.
|
||||
//strategy: 'implicit',
|
||||
// strategy: 'implicit',
|
||||
|
||||
// contracts: {
|
||||
// Discover: {
|
||||
// args: { _SNT: '0x744d70fdbe2ba4cf95131626614a1763df805b9e' },
|
||||
// },
|
||||
// MiniMeToken: { deploy: false },
|
||||
// TestBancorFormula: { deploy: false },
|
||||
// },
|
||||
|
||||
contracts: {
|
||||
Discover: {
|
||||
args: { _SNT: "0x744d70fdbe2ba4cf95131626614a1763df805b9e" }
|
||||
MiniMeToken: { deploy: false },
|
||||
TestBancorFormula: { deploy: false },
|
||||
MiniMeTokenFactory: {},
|
||||
SNT: {
|
||||
instanceOf: 'MiniMeToken',
|
||||
args: [
|
||||
'$MiniMeTokenFactory',
|
||||
'0x0000000000000000000000000000000000000000',
|
||||
0,
|
||||
'TestMiniMeToken',
|
||||
18,
|
||||
'SNT',
|
||||
true,
|
||||
],
|
||||
},
|
||||
MiniMeToken: { "deploy": false },
|
||||
TestBancorFormula: { "deploy": false }
|
||||
}
|
||||
Discover: {
|
||||
args: ['$SNT'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// default environment, merges with the settings in default
|
||||
// assumed to be the intended environment by `embark run`
|
||||
development: {
|
||||
dappConnection: [
|
||||
"ws://localhost:8546",
|
||||
"http://localhost:8545",
|
||||
"$WEB3" // uses pre existing web3 object if available (e.g in Mist)
|
||||
]
|
||||
'ws://localhost:8546',
|
||||
'http://localhost:8545',
|
||||
'$WEB3', // uses pre existing web3 object if available (e.g in Mist)
|
||||
],
|
||||
},
|
||||
|
||||
// merges with the settings in default
|
||||
// used with "embark run privatenet"
|
||||
privatenet: {
|
||||
},
|
||||
privatenet: {},
|
||||
|
||||
// merges with the settings in default
|
||||
// used with "embark run testnet"
|
||||
testnet: {
|
||||
},
|
||||
testnet: {},
|
||||
|
||||
// merges with the settings in default
|
||||
// used with "embark run livenet"
|
||||
livenet: {
|
||||
},
|
||||
livenet: {},
|
||||
|
||||
// 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: {
|
||||
// }
|
||||
}
|
||||
|
2
config/development/mnemonic.js
Normal file
2
config/development/mnemonic.js
Normal file
@ -0,0 +1,2 @@
|
||||
module.exports.mnemonic =
|
||||
'artefact rebuild liquid honey sport clean candy motor cereal job gap series'
|
@ -42,7 +42,7 @@ contract Discover is ApproveAndCallFallBack, BancorFormula {
|
||||
|
||||
Data[] public dapps;
|
||||
mapping(bytes32 => uint) public id2index;
|
||||
mapping(bytes32 => bool) existingIDs;
|
||||
mapping(bytes32 => bool) public existingIDs;
|
||||
|
||||
event DAppCreated(bytes32 indexed id, uint newEffectiveBalance);
|
||||
event Upvote(bytes32 indexed id, uint newEffectiveBalance);
|
||||
@ -237,6 +237,15 @@ contract Discover is ApproveAndCallFallBack, BancorFormula {
|
||||
return (mEBalance.sub(d.effectiveBalance));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @dev Used in UI in order to fetch all dapps
|
||||
* @return dapps count
|
||||
*/
|
||||
function getDAppsCount() external view returns(uint) {
|
||||
return dapps.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Downvotes always remove 1% of the current ranking.
|
||||
* @param _id bytes32 unique identifier.
|
||||
|
@ -22,5 +22,5 @@
|
||||
"optimize-runs": 200
|
||||
}
|
||||
},
|
||||
"generationDir": "embarkArtifacts"
|
||||
"generationDir": "src/embarkArtifacts"
|
||||
}
|
@ -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",
|
||||
|
28
src/common/blockchain/index.js
Normal file
28
src/common/blockchain/index.js
Normal file
@ -0,0 +1,28 @@
|
||||
import utils from './utils'
|
||||
import SNTService from './services/contracts-services/snt-service/snt-service'
|
||||
import DiscoverService from './services/contracts-services/discover-service/discover-service'
|
||||
|
||||
import BlockchainConfig from './services/config'
|
||||
|
||||
const init = function() {
|
||||
try {
|
||||
BlockchainConfig()
|
||||
|
||||
const sharedContext = {
|
||||
account: '',
|
||||
}
|
||||
|
||||
sharedContext.SNTService = new SNTService(sharedContext)
|
||||
sharedContext.DiscoverService = new DiscoverService(sharedContext)
|
||||
|
||||
return {
|
||||
SNTService: sharedContext.SNTService,
|
||||
DiscoverService: sharedContext.DiscoverService,
|
||||
utils,
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
export default { init, utils }
|
28
src/common/blockchain/ipfs/helpers.js
Normal file
28
src/common/blockchain/ipfs/helpers.js
Normal file
@ -0,0 +1,28 @@
|
||||
import bs58 from 'bs58'
|
||||
|
||||
export const base64ToBlob = base64Text => {
|
||||
const byteString = atob(base64Text)
|
||||
|
||||
const arrayBuffer = new ArrayBuffer(byteString.length)
|
||||
const uintArray = new Uint8Array(arrayBuffer)
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
uintArray[i] = byteString.charCodeAt(i)
|
||||
}
|
||||
|
||||
return new Blob([arrayBuffer])
|
||||
}
|
||||
|
||||
export const getBytes32FromIpfsHash = ipfsListing => {
|
||||
const decodedHash = bs58
|
||||
.decode(ipfsListing)
|
||||
.slice(2)
|
||||
.toString('hex')
|
||||
return `0x${decodedHash}`
|
||||
}
|
||||
|
||||
export const getIpfsHashFromBytes32 = bytes32Hex => {
|
||||
const hashHex = `1220${bytes32Hex.slice(2)}`
|
||||
const hashBytes = Buffer.from(hashHex, 'hex')
|
||||
const hashStr = bs58.encode(hashBytes)
|
||||
return hashStr
|
||||
}
|
63
src/common/blockchain/ipfs/index.js
Normal file
63
src/common/blockchain/ipfs/index.js
Normal file
@ -0,0 +1,63 @@
|
||||
import * as helpers from './helpers'
|
||||
import EmbarkJSService from '../services/embark-service/embark-service'
|
||||
|
||||
const checkIPFSAvailability = async () => {
|
||||
const isAvailable = await EmbarkJSService.Storage.isAvailable()
|
||||
if (!isAvailable) {
|
||||
throw new Error('IPFS Storage is unavailable')
|
||||
}
|
||||
}
|
||||
|
||||
const uploadImage = async base64Image => {
|
||||
const imageFile = [
|
||||
{
|
||||
files: [helpers.base64ToBlob(base64Image)],
|
||||
},
|
||||
]
|
||||
|
||||
return EmbarkJSService.Storage.uploadFile(imageFile)
|
||||
}
|
||||
|
||||
const uploadMetadata = async metadata => {
|
||||
const hash = await EmbarkJSService.Storage.saveText(metadata)
|
||||
return helpers.getBytes32FromIpfsHash(hash)
|
||||
}
|
||||
|
||||
export const uploadDAppMetadata = async metadata => {
|
||||
try {
|
||||
await checkIPFSAvailability()
|
||||
|
||||
metadata.image = await uploadImage(metadata.image)
|
||||
const uploadedMetadataHash = await uploadMetadata(JSON.stringify(metadata))
|
||||
|
||||
return uploadedMetadataHash
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Uploading DApp metadata to IPFS failed. Details: ${error.message}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const retrieveMetadata = async metadataBytes32 => {
|
||||
const metadataHash = helpers.getIpfsHashFromBytes32(metadataBytes32)
|
||||
return EmbarkJSService.Storage.get(metadataHash)
|
||||
}
|
||||
|
||||
const retrieveImageUrl = async imageHash => {
|
||||
return EmbarkJSService.Storage.getUrl(imageHash)
|
||||
}
|
||||
|
||||
export const retrieveDAppMetadataByHash = async metadataBytes32 => {
|
||||
try {
|
||||
await checkIPFSAvailability()
|
||||
|
||||
const metadata = JSON.parse(await retrieveMetadata(metadataBytes32))
|
||||
metadata.image = await retrieveImageUrl(metadata.image)
|
||||
|
||||
return metadata
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Fetching metadata from IPFS failed. Details: ${error.message}`,
|
||||
)
|
||||
}
|
||||
}
|
9
src/common/blockchain/services/config.js
Normal file
9
src/common/blockchain/services/config.js
Normal file
@ -0,0 +1,9 @@
|
||||
/* global web3 */
|
||||
import Web3 from '../../../embarkArtifacts/modules/web3'
|
||||
|
||||
// Todo: Should be moved to .env
|
||||
const RPC_URL = 'http://localhost:8545'
|
||||
|
||||
export default function() {
|
||||
web3 = new Web3(new Web3.providers.HttpProvider(RPC_URL))
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/* global web3 */
|
||||
|
||||
import EmbarkJSService from '../embark-service/embark-service'
|
||||
|
||||
class BlockchainService {
|
||||
constructor(sharedContext, contract, Validator) {
|
||||
this.contract = contract.address
|
||||
contract.setProvider(web3.currentProvider)
|
||||
|
||||
this.sharedContext = sharedContext
|
||||
this.validator = new Validator(this)
|
||||
}
|
||||
|
||||
async __unlockServiceAccount() {
|
||||
// const accounts = await EmbarkJS.Blockchain.Providers.web3.getAccounts()
|
||||
// // if (accounts.length > 0) {
|
||||
// this.sharedContext.account = accounts[0]
|
||||
// } else {
|
||||
// const provider = global.web3.currentProvider
|
||||
// Check for undefined
|
||||
// console.log(await global.web3.eth.getAccounts())
|
||||
const accounts = await EmbarkJSService.enableEthereum()
|
||||
if (accounts) {
|
||||
this.sharedContext.account = accounts[0]
|
||||
}
|
||||
// global.web3.setProvider(provider)
|
||||
// }
|
||||
|
||||
// throw new Error('Could not unlock an account or web3 is missing')
|
||||
}
|
||||
}
|
||||
|
||||
export default BlockchainService
|
@ -0,0 +1,148 @@
|
||||
/* global web3 */
|
||||
import { broadcastContractFn } from '../helpers'
|
||||
|
||||
import * as ipfsSDK from '../../../ipfs'
|
||||
import BlockchainService from '../blockchain-service'
|
||||
|
||||
import DiscoverValidator from './discover-validator'
|
||||
import DiscoverContract from '../../../../../embarkArtifacts/contracts/Discover'
|
||||
|
||||
class DiscoverService extends BlockchainService {
|
||||
constructor(sharedContext) {
|
||||
super(sharedContext, DiscoverContract, DiscoverValidator)
|
||||
}
|
||||
|
||||
// View methods
|
||||
async upVoteEffect(id, amount) {
|
||||
await this.validator.validateUpVoteEffect(id, amount)
|
||||
|
||||
return DiscoverContract.methods.upvoteEffect(id, amount).call()
|
||||
}
|
||||
|
||||
async downVoteCost(id) {
|
||||
const dapp = await this.getDAppById(id)
|
||||
return DiscoverContract.methods.downvoteCost(dapp.id).call()
|
||||
}
|
||||
|
||||
// Todo: Should be implemented
|
||||
// async getDApps() {
|
||||
// const dapps = []
|
||||
// const dappsCount = await DiscoverContract.methods.getDAppsCount().call()
|
||||
|
||||
// for (let i = 0; i < dappsCount; i++) {
|
||||
// const dapp = await DiscoverContract.methods.dapps(i).call()
|
||||
// }
|
||||
// }
|
||||
|
||||
async getDAppById(id) {
|
||||
let dapp
|
||||
try {
|
||||
const dappId = await DiscoverContract.methods.id2index(id).call()
|
||||
dapp = await DiscoverContract.methods.dapps(dappId).call()
|
||||
} catch (error) {
|
||||
throw new Error('Searching DApp does not exists')
|
||||
}
|
||||
|
||||
if (dapp.id != id) {
|
||||
throw new Error('Error fetching correct data from contract')
|
||||
}
|
||||
|
||||
return dapp
|
||||
}
|
||||
|
||||
async getDAppDataById(id) {
|
||||
const dapp = await this.getDAppById(id)
|
||||
|
||||
try {
|
||||
dapp.metadata = await ipfsSDK.retrieveDAppMetadataByHash(dapp.metadata)
|
||||
return dapp
|
||||
} catch (error) {
|
||||
throw new Error('Error fetching correct data from IPFS')
|
||||
}
|
||||
}
|
||||
|
||||
async safeMax() {
|
||||
return DiscoverContract.methods.safeMax().call()
|
||||
}
|
||||
|
||||
async isDAppExists(id) {
|
||||
return DiscoverContract.methods.existingIDs(id).call()
|
||||
}
|
||||
|
||||
// Transaction methods
|
||||
async createDApp(amount, metadata) {
|
||||
const dappMetadata = JSON.parse(JSON.stringify(metadata))
|
||||
const dappId = web3.utils.keccak256(JSON.stringify(dappMetadata))
|
||||
|
||||
await this.validator.validateDAppCreation(dappId, amount)
|
||||
|
||||
const uploadedMetadata = await ipfsSDK.uploadDAppMetadata(dappMetadata)
|
||||
|
||||
const callData = DiscoverContract.methods
|
||||
.createDApp(dappId, amount, uploadedMetadata)
|
||||
.encodeABI()
|
||||
|
||||
const createdTx = await this.sharedContext.SNTService.approveAndCall(
|
||||
this.contract,
|
||||
amount,
|
||||
callData,
|
||||
)
|
||||
|
||||
return { tx: createdTx, id: dappId }
|
||||
}
|
||||
|
||||
async upVote(id, amount) {
|
||||
await this.validator.validateUpVoting(id, amount)
|
||||
|
||||
const callData = DiscoverContract.methods.upvote(id, amount).encodeABI()
|
||||
return this.sharedContext.SNTService.approveAndCall(
|
||||
this.contract,
|
||||
amount,
|
||||
callData,
|
||||
)
|
||||
}
|
||||
|
||||
async downVote(id, amount) {
|
||||
await this.validator.validateDownVoting(id, amount)
|
||||
|
||||
const callData = DiscoverContract.methods.downvote(id, amount).encodeABI()
|
||||
return this.sharedContext.SNTService.approveAndCall(
|
||||
this.contract,
|
||||
amount,
|
||||
callData,
|
||||
)
|
||||
}
|
||||
|
||||
async withdraw(id, amount) {
|
||||
await super.__unlockServiceAccount()
|
||||
await this.validator.validateWithdrawing(id, amount)
|
||||
|
||||
try {
|
||||
return broadcastContractFn(
|
||||
DiscoverContract.methods.withdraw(id, amount).send,
|
||||
this.sharedContext.account,
|
||||
)
|
||||
} catch (error) {
|
||||
throw new Error(`Transfer on withdraw failed. Details: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async setMetadata(id, metadata) {
|
||||
await super.__unlockServiceAccount()
|
||||
await this.validator.validateMetadataSet(id)
|
||||
|
||||
const dappMetadata = JSON.parse(JSON.stringify(metadata))
|
||||
const uploadedMetadata = await ipfsSDK.uploadDAppMetadata(dappMetadata)
|
||||
|
||||
try {
|
||||
return broadcastContractFn(
|
||||
DiscoverContract.methods.setMetadata(id, uploadedMetadata).send,
|
||||
this.sharedContext.account,
|
||||
)
|
||||
} catch (error) {
|
||||
throw new Error(`Uploading metadata failed. Details: ${error.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DiscoverService
|
@ -0,0 +1,76 @@
|
||||
class DiscoverValidator {
|
||||
constructor(service) {
|
||||
this.service = service
|
||||
}
|
||||
|
||||
async validateUpVoteEffect(id, amount) {
|
||||
const dapp = await this.service.getDAppById(id)
|
||||
|
||||
const safeMax = await this.service.safeMax()
|
||||
if (Number(dapp.balance) + amount > safeMax) {
|
||||
throw new Error(
|
||||
`You cannot upvote by this much, try with a lower amount. Maximum upvote amount:
|
||||
${Number(safeMax) - Number(dapp.balance)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
||||
async validateUpVoting(id, amount) {
|
||||
await this.validateUpVoteEffect(id, amount)
|
||||
|
||||
if (amount <= 0) {
|
||||
throw new Error('You must send some SNT in order to upvote')
|
||||
}
|
||||
}
|
||||
|
||||
async validateDownVoting(id, amount) {
|
||||
const dapp = await this.service.getDAppById(id)
|
||||
|
||||
const downVoteCost = await this.service.downVoteCost(dapp.id)
|
||||
if (downVoteCost.c != amount) {
|
||||
throw new Error('Incorrect amount: valid if effect on ranking is 1%')
|
||||
}
|
||||
}
|
||||
|
||||
async validateWithdrawing(id, amount) {
|
||||
const dapp = await this.service.getDAppById(id)
|
||||
|
||||
if (dapp.developer.toLowerCase() != this.service.sharedContext.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.toLowerCase() != this.service.sharedContext.account) {
|
||||
throw new Error('Only the developer can update the metadata')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default DiscoverValidator
|
@ -0,0 +1,7 @@
|
||||
export const broadcastContractFn = (contractMethod, account) => {
|
||||
return new Promise(resolve => {
|
||||
contractMethod({ from: account }).on('transactionHash', hash => {
|
||||
resolve(hash)
|
||||
})
|
||||
})
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import { broadcastContractFn } from '../helpers'
|
||||
|
||||
import BlockchainService from '../blockchain-service'
|
||||
|
||||
import SNTValidator from './snt-validator'
|
||||
import SNTToken from '../../../../../embarkArtifacts/contracts/SNT'
|
||||
|
||||
class SNTService extends BlockchainService {
|
||||
constructor(sharedContext) {
|
||||
super(sharedContext, SNTToken, SNTValidator)
|
||||
}
|
||||
|
||||
async allowance(from, to) {
|
||||
return SNTToken.methods.allowance(from, to).call()
|
||||
}
|
||||
|
||||
async balanceOf(account) {
|
||||
return SNTToken.methods.balanceOf(account).call()
|
||||
}
|
||||
|
||||
async controller() {
|
||||
return SNTToken.methods.controller().call()
|
||||
}
|
||||
|
||||
async transferable() {
|
||||
return SNTToken.methods.transfersEnabled().call()
|
||||
}
|
||||
|
||||
async approveAndCall(spender, amount, callData) {
|
||||
await super.__unlockServiceAccount()
|
||||
await this.validator.validateApproveAndCall(spender, amount)
|
||||
|
||||
return broadcastContractFn(
|
||||
SNTToken.methods.approveAndCall(spender, amount, callData).send,
|
||||
this.sharedContext.account,
|
||||
)
|
||||
}
|
||||
|
||||
// This is for testing purpose only
|
||||
async generateTokens() {
|
||||
await super.__unlockServiceAccount()
|
||||
|
||||
await SNTToken.methods
|
||||
.generateTokens(this.sharedContext.account, 10000)
|
||||
.send({ from: this.sharedContext.account })
|
||||
}
|
||||
}
|
||||
|
||||
export default SNTService
|
@ -0,0 +1,35 @@
|
||||
class SNTValidator {
|
||||
constructor(service) {
|
||||
this.service = service
|
||||
}
|
||||
|
||||
async validateSNTTransferFrom(amount) {
|
||||
const toBalance = await this.service.balanceOf(
|
||||
this.service.sharedContext.account,
|
||||
)
|
||||
|
||||
if (toBalance < amount) {
|
||||
throw new Error('Not enough SNT balance')
|
||||
}
|
||||
}
|
||||
|
||||
async validateApproveAndCall(spender, amount) {
|
||||
const isTransferableToken = await this.service.transferable()
|
||||
if (!isTransferableToken) {
|
||||
throw new Error('Token is not transferable')
|
||||
}
|
||||
|
||||
await this.validateSNTTransferFrom(amount)
|
||||
|
||||
const allowance = await this.service.allowance(
|
||||
this.service.sharedContext.account,
|
||||
spender,
|
||||
)
|
||||
|
||||
if (amount != 0 && allowance != 0) {
|
||||
throw new Error('You have allowance already')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SNTValidator
|
@ -0,0 +1,14 @@
|
||||
import EmbarkJS from '../../../../embarkArtifacts/embarkjs'
|
||||
|
||||
class EmbarkService {
|
||||
constructor() {
|
||||
if (!EmbarkService.instance) {
|
||||
EmbarkJS.Storage.setProvider('ipfs')
|
||||
EmbarkService.instance = EmbarkJS
|
||||
}
|
||||
|
||||
return EmbarkService.instance
|
||||
}
|
||||
}
|
||||
|
||||
export default new EmbarkService()
|
20
src/common/blockchain/utils.js
Normal file
20
src/common/blockchain/utils.js
Normal file
@ -0,0 +1,20 @@
|
||||
/* global web3 */
|
||||
|
||||
const TRANSACTION_STATUSES = {
|
||||
Failed: 0,
|
||||
Successful: 1,
|
||||
Pending: 2,
|
||||
}
|
||||
|
||||
export default {
|
||||
getTxStatus: async txHash => {
|
||||
const txReceipt = await web3.eth.getTransactionReceipt(txHash)
|
||||
if (txReceipt) {
|
||||
return txReceipt.status
|
||||
? TRANSACTION_STATUSES.Successful
|
||||
: TRANSACTION_STATUSES.Failed
|
||||
}
|
||||
|
||||
return TRANSACTION_STATUSES.Pending
|
||||
},
|
||||
}
|
29
src/common/utils/number-formatter.js
Normal file
29
src/common/utils/number-formatter.js
Normal file
@ -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
|
@ -10,6 +10,8 @@ import Submit from '../Submit'
|
||||
import Terms from '../Terms/Terms'
|
||||
import TransactionStatus from '../TransactionStatus'
|
||||
|
||||
import Example from '../BlockchainExample'
|
||||
|
||||
class Router extends React.Component {
|
||||
componentDidMount() {
|
||||
const { fetchHighestRanked, fetchRecentlyAdded } = this.props
|
||||
@ -25,6 +27,7 @@ class Router extends React.Component {
|
||||
<Route path="/all" component={Dapps} />
|
||||
<Route path="/recently-added" component={RecentlyAdded} />
|
||||
<Route path="/terms" component={Terms} />
|
||||
<Route path="/example" component={Example} />
|
||||
</Switch>,
|
||||
<Vote key={2} />,
|
||||
<Submit key={3} />,
|
||||
|
@ -0,0 +1,4 @@
|
||||
import { connect } from 'react-redux'
|
||||
import BlockchainExample from './BlockchainExample'
|
||||
|
||||
export default connect()(BlockchainExample)
|
107
src/modules/BlockchainExample/BlockchainExample.jsx
Normal file
107
src/modules/BlockchainExample/BlockchainExample.jsx
Normal file
@ -0,0 +1,107 @@
|
||||
import React from 'react'
|
||||
import exampleImage from './dapp.image'
|
||||
|
||||
import BlockchainSDK from '../../common/blockchain'
|
||||
|
||||
const SERVICES = BlockchainSDK.init()
|
||||
|
||||
const DAPP_DATA = {
|
||||
name: 'Test1',
|
||||
url: 'https://www.test1.com/',
|
||||
description: 'Decentralized Test DApp',
|
||||
category: 'test',
|
||||
dateCreated: Date.now(),
|
||||
image: exampleImage.image,
|
||||
}
|
||||
|
||||
// setTimeout is used in order to wait a transaction to be mined
|
||||
const getResult = async function(method, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
setTimeout(async () => {
|
||||
const result = await SERVICES.DiscoverService[method](...params)
|
||||
resolve(result)
|
||||
}, 2000)
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
Each transaction-function return tx hash
|
||||
createDApp returns tx hash + dapp id
|
||||
*/
|
||||
class Example extends React.Component {
|
||||
async getFullDApp(id) {
|
||||
return getResult('getDAppDataById', [id])
|
||||
}
|
||||
|
||||
async createDApp() {
|
||||
await SERVICES.SNTService.generateTokens()
|
||||
return SERVICES.DiscoverService.createDApp(10000, DAPP_DATA)
|
||||
}
|
||||
|
||||
async upvote(id) {
|
||||
return getResult('upVote', [id, 1000])
|
||||
}
|
||||
|
||||
async downvote(id, amount) {
|
||||
return getResult('downVote', [id, amount])
|
||||
}
|
||||
|
||||
async withdraw(id) {
|
||||
return getResult('withdraw', [id, 500])
|
||||
}
|
||||
|
||||
async upVoteEffect(id) {
|
||||
return getResult('upVoteEffect', [id, 10000])
|
||||
}
|
||||
|
||||
async downVoteCost(id) {
|
||||
return getResult('downVoteCost', [id])
|
||||
}
|
||||
|
||||
async setMetadata(id) {
|
||||
DAPP_DATA.category = 'updated'
|
||||
return getResult('setMetadata', [id, DAPP_DATA])
|
||||
}
|
||||
|
||||
async logDiscoverMethods() {
|
||||
const createdDApp = await this.createDApp()
|
||||
|
||||
const dappData = await this.getFullDApp(createdDApp.id)
|
||||
console.log(`Created DApp : ${JSON.stringify(dappData)}`)
|
||||
|
||||
document.getElementById('testImage').src = dappData.metadata.image
|
||||
|
||||
const downVote = await this.downVoteCost(createdDApp.id)
|
||||
console.log(
|
||||
`Downvote TX Hash : ${await this.downvote(createdDApp.id, downVote.c)}`,
|
||||
)
|
||||
console.log(`Upvote TX Hash : ${await this.upvote(createdDApp.id)}`)
|
||||
console.log(`Withdraw TX Hash : ${await this.withdraw(createdDApp.id)}`)
|
||||
console.log(
|
||||
`UpvoteEffect Result : ${await this.upVoteEffect(createdDApp.id)}`,
|
||||
)
|
||||
console.log(
|
||||
`DownVoteCost Result : ${await this.downVoteCost(createdDApp.id)}`,
|
||||
)
|
||||
|
||||
console.log(
|
||||
`Set metadata TX Hash : ${await this.setMetadata(createdDApp.id)}`,
|
||||
)
|
||||
console.log(
|
||||
`Updated DApp : ${JSON.stringify(
|
||||
await this.getFullDApp(createdDApp.id),
|
||||
)}`,
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h1 onLoad={this.logDiscoverMethods()} />
|
||||
<img id="testImage" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Example
|
3
src/modules/BlockchainExample/dapp.image.json
Normal file
3
src/modules/BlockchainExample/dapp.image.json
Normal file
File diff suppressed because one or more lines are too long
3
src/modules/BlockchainExample/index.js
Normal file
3
src/modules/BlockchainExample/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import BlockchainExample from './BlockchainExample.container'
|
||||
|
||||
export default BlockchainExample
|
@ -1,156 +1,176 @@
|
||||
/*global assert, web3*/
|
||||
const bs58 = require('bs58');
|
||||
/*global assert, web3 */
|
||||
const bs58 = require('bs58')
|
||||
|
||||
// This has been tested with the real Ethereum network and Testrpc.
|
||||
// Copied and edited from: https://gist.github.com/xavierlepretre/d5583222fde52ddfbc58b7cfa0d2d0a9
|
||||
|
||||
exports.assertReverts = (contractMethodCall, maxGasAvailable) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
resolve(contractMethodCall());
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
resolve(contractMethodCall())
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
.then(tx => {
|
||||
assert.equal(
|
||||
tx.receipt.gasUsed,
|
||||
maxGasAvailable,
|
||||
'tx successful, the max gas available was not consumed',
|
||||
)
|
||||
})
|
||||
.catch(error => {
|
||||
if (
|
||||
String(error).indexOf('invalid opcode') < 0 &&
|
||||
String(error).indexOf('out of gas') < 0
|
||||
) {
|
||||
// Checks if the error is from TestRpc. If it is then ignore it.
|
||||
// Otherwise relay/throw the error produced by the above assertion.
|
||||
// Note that no error is thrown when using a real Ethereum network AND the assertion above is true.
|
||||
throw error
|
||||
}
|
||||
})
|
||||
.then(tx => {
|
||||
assert.equal(tx.receipt.gasUsed, maxGasAvailable, "tx successful, the max gas available was not consumed");
|
||||
})
|
||||
.catch(error => {
|
||||
if ((String(error)).indexOf("invalid opcode") < 0 && (String(error)).indexOf("out of gas") < 0) {
|
||||
// Checks if the error is from TestRpc. If it is then ignore it.
|
||||
// Otherwise relay/throw the error produced by the above assertion.
|
||||
// Note that no error is thrown when using a real Ethereum network AND the assertion above is true.
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.listenForEvent = event => new Promise((resolve, reject) => {
|
||||
}
|
||||
|
||||
exports.listenForEvent = event =>
|
||||
new Promise((resolve, reject) => {
|
||||
event({}, (error, response) => {
|
||||
if (!error) {
|
||||
resolve(response.args);
|
||||
resolve(response.args)
|
||||
} else {
|
||||
reject(error);
|
||||
reject(error)
|
||||
}
|
||||
event.stopWatching();
|
||||
});
|
||||
});
|
||||
|
||||
exports.eventValues = (receipt, eventName) => {
|
||||
if (receipt.events[eventName]) return receipt.events[eventName].returnValues;
|
||||
};
|
||||
|
||||
exports.addressToBytes32 = (address) => {
|
||||
const stringed = "0000000000000000000000000000000000000000000000000000000000000000" + address.slice(2);
|
||||
return "0x" + stringed.substring(stringed.length - 64, stringed.length);
|
||||
};
|
||||
|
||||
// OpenZeppelin's expectThrow helper -
|
||||
// Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js
|
||||
exports.expectThrow = async promise => {
|
||||
try {
|
||||
await promise;
|
||||
} catch (error) {
|
||||
// TODO: Check jump destination to destinguish between a throw
|
||||
// and an actual invalid jump.
|
||||
const invalidOpcode = error.message.search('invalid opcode') >= 0;
|
||||
// TODO: When we contract A calls contract B, and B throws, instead
|
||||
// of an 'invalid jump', we get an 'out of gas' error. How do
|
||||
// we distinguish this from an actual out of gas event? (The
|
||||
// testrpc log actually show an 'invalid jump' event.)
|
||||
const outOfGas = error.message.search('out of gas') >= 0;
|
||||
const revert = error.message.search('revert') >= 0;
|
||||
assert(
|
||||
invalidOpcode || outOfGas || revert,
|
||||
'Expected throw, got \'' + error + '\' instead',
|
||||
);
|
||||
return;
|
||||
}
|
||||
assert.fail('Expected throw not received');
|
||||
};
|
||||
|
||||
|
||||
exports.assertJump = (error) => {
|
||||
assert(error.message.search('VM Exception while processing transaction: revert') > -1, 'Revert should happen');
|
||||
};
|
||||
|
||||
|
||||
function callbackToResolve(resolve, reject) {
|
||||
return function(error, value) {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
exports.promisify = (func) =>
|
||||
(...args) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = (err, data) => err ? reject(err) : resolve(data);
|
||||
func.apply(this, [...args, callback]);
|
||||
});
|
||||
};
|
||||
|
||||
exports.zeroAddress = '0x0000000000000000000000000000000000000000';
|
||||
exports.zeroBytes32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
||||
exports.timeUnits = {
|
||||
seconds: 1,
|
||||
minutes: 60,
|
||||
hours: 60 * 60,
|
||||
days: 24 * 60 * 60,
|
||||
weeks: 7 * 24 * 60 * 60,
|
||||
years: 365 * 24 * 60 * 60
|
||||
};
|
||||
|
||||
exports.ensureException = function(error) {
|
||||
assert(isException(error), error.toString());
|
||||
};
|
||||
|
||||
function isException(error) {
|
||||
let strError = error.toString();
|
||||
return strError.includes('invalid opcode') || strError.includes('invalid JUMP') || strError.includes('revert');
|
||||
}
|
||||
|
||||
const evmMethod = (method, params = []) => {
|
||||
return new Promise(function(resolve, reject) {
|
||||
const sendMethod = (web3.currentProvider.sendAsync) ? web3.currentProvider.sendAsync.bind(web3.currentProvider) : web3.currentProvider.send.bind(web3.currentProvider);
|
||||
sendMethod(
|
||||
{
|
||||
jsonrpc: '2.0',
|
||||
method,
|
||||
params,
|
||||
id: new Date().getSeconds()
|
||||
},
|
||||
(error, res) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
resolve(res.result);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
exports.evmSnapshot = async () => {
|
||||
const result = await evmMethod("evm_snapshot");
|
||||
return web3.utils.hexToNumber(result);
|
||||
};
|
||||
|
||||
exports.evmRevert = (id) => {
|
||||
const params = [id];
|
||||
return evmMethod("evm_revert", params);
|
||||
};
|
||||
|
||||
exports.increaseTime = async (amount) => {
|
||||
await evmMethod("evm_increaseTime", [Number(amount)]);
|
||||
await evmMethod("evm_mine");
|
||||
};
|
||||
event.stopWatching()
|
||||
})
|
||||
})
|
||||
|
||||
exports.eventValues = (receipt, eventName) => {
|
||||
if (receipt.events[eventName]) return receipt.events[eventName].returnValues
|
||||
}
|
||||
|
||||
exports.addressToBytes32 = address => {
|
||||
const stringed =
|
||||
'0000000000000000000000000000000000000000000000000000000000000000' +
|
||||
address.slice(2)
|
||||
return `0x${ stringed.substring(stringed.length - 64, stringed.length)}`;
|
||||
}
|
||||
|
||||
// OpenZeppelin's expectThrow helper -
|
||||
// Source: https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/test/helpers/expectThrow.js
|
||||
exports.expectThrow = async promise => {
|
||||
try {
|
||||
await promise
|
||||
} catch (error) {
|
||||
// TODO: Check jump destination to destinguish between a throw
|
||||
// and an actual invalid jump.
|
||||
const invalidOpcode = error.message.search('invalid opcode') >= 0
|
||||
// TODO: When we contract A calls contract B, and B throws, instead
|
||||
// of an 'invalid jump', we get an 'out of gas' error. How do
|
||||
// we distinguish this from an actual out of gas event? (The
|
||||
// testrpc log actually show an 'invalid jump' event.)
|
||||
const outOfGas = error.message.search('out of gas') >= 0
|
||||
const revert = error.message.search('revert') >= 0
|
||||
assert(
|
||||
invalidOpcode || outOfGas || revert,
|
||||
`Expected throw, got '${ error }' instead`,
|
||||
)
|
||||
return
|
||||
}
|
||||
assert.fail('Expected throw not received')
|
||||
}
|
||||
|
||||
exports.assertJump = error => {
|
||||
assert(
|
||||
error.message.search('VM Exception while processing transaction: revert') >
|
||||
-1,
|
||||
'Revert should happen',
|
||||
)
|
||||
}
|
||||
|
||||
function callbackToResolve(resolve, reject) {
|
||||
return function(error, value) {
|
||||
if (error) {
|
||||
reject(error)
|
||||
} else {
|
||||
resolve(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.promisify = func => (...args) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callback = (err, data) => (err ? reject(err) : resolve(data))
|
||||
func.apply(this, [...args, callback])
|
||||
})
|
||||
}
|
||||
|
||||
exports.zeroAddress = '0x0000000000000000000000000000000000000000'
|
||||
exports.zeroBytes32 =
|
||||
'0x0000000000000000000000000000000000000000000000000000000000000000'
|
||||
exports.timeUnits = {
|
||||
seconds: 1,
|
||||
minutes: 60,
|
||||
hours: 60 * 60,
|
||||
days: 24 * 60 * 60,
|
||||
weeks: 7 * 24 * 60 * 60,
|
||||
years: 365 * 24 * 60 * 60,
|
||||
}
|
||||
|
||||
exports.ensureException = function(error) {
|
||||
assert(isException(error), error.toString())
|
||||
}
|
||||
|
||||
function isException(error) {
|
||||
const strError = error.toString()
|
||||
return (
|
||||
strError.includes('invalid opcode') ||
|
||||
strError.includes('invalid JUMP') ||
|
||||
strError.includes('revert')
|
||||
)
|
||||
}
|
||||
|
||||
const evmMethod = (method, params = []) => {
|
||||
return new Promise(function(resolve, reject) {
|
||||
const sendMethod = web3.currentProvider.sendAsync
|
||||
? web3.currentProvider.sendAsync.bind(web3.currentProvider)
|
||||
: web3.currentProvider.send.bind(web3.currentProvider)
|
||||
sendMethod(
|
||||
{
|
||||
jsonrpc: '2.0',
|
||||
method,
|
||||
params,
|
||||
id: new Date().getSeconds(),
|
||||
},
|
||||
(error, res) => {
|
||||
if (error) {
|
||||
return reject(error)
|
||||
}
|
||||
resolve(res.result)
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
exports.evmSnapshot = async () => {
|
||||
const result = await evmMethod('evm_snapshot')
|
||||
return web3.utils.hexToNumber(result)
|
||||
}
|
||||
|
||||
exports.evmRevert = id => {
|
||||
const params = [id]
|
||||
return evmMethod('evm_revert', params)
|
||||
}
|
||||
|
||||
exports.increaseTime = async amount => {
|
||||
await evmMethod('evm_increaseTime', [Number(amount)])
|
||||
await evmMethod('evm_mine')
|
||||
}
|
||||
|
||||
exports.getBytes32FromIpfsHash = ipfsListing => {
|
||||
const decodedHash = bs58.decode(ipfsListing).slice(2).toString('hex')
|
||||
const decodedHash = bs58
|
||||
.decode(ipfsListing)
|
||||
.slice(2)
|
||||
.toString('hex')
|
||||
return `0x${decodedHash}`
|
||||
}
|
||||
|
||||
@ -159,4 +179,4 @@ exports.getIpfsHashFromBytes32 = bytes32Hex => {
|
||||
const hashBytes = Buffer.from(hashHex, 'hex')
|
||||
const hashStr = bs58.encode(hashBytes)
|
||||
return hashStr
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user