Merge pull request #6 from PombeirP/cleanup

Migrate to ES6 syntax, fix console logging, fix token sends
This commit is contained in:
Andy Tudhope 2018-03-20 12:46:59 +02:00 committed by GitHub
commit 56acf5c435
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 2930 additions and 466 deletions

3
.eslintrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "standard"
}

View File

@ -1,53 +1,44 @@
'use strict'
const https = require('https');
const config = require('../config');
const https = require('https')
const config = require('../config')
const bot = require('../bot')
// Returns the url for getting the labels of a request (Github API v3)
// req has req.issue.labels_url
const getLabelsURL = function (req) {
let url = req.body.issue.labels_url;
function getLabelsURL (req) {
// Make the URL generic removing the name of the label
return url.replace('{/name}', '');
}
// Returns the bounty labelNames of the request, only for testing motives
const getLabelsMock = function (req) {
return new Promise((resolve, reject) => { resolve(req.body.issue.labels) });
return req.body.issue.labels_url.replace('{/name}', '')
}
// Returns all the bounty labelNames of a given issue (Github API v3)
const getLabels = function (req) {
if (config.debug) {
return getLabelsMock(req);
} else {
const path = getLabelsURL(req).replace('https://api.github.com', '');
function getLabels (req) {
const path = getLabelsURL(req).replace('https://api.github.com', '')
const options = {
hostname: 'api.github.com',
path: path,
headers: { 'User-Agent': config.githubUsername }
};
}
return new Promise((resolve, reject) => {
const request = https.get(options, (response) => {
// handle http errors
if (response.statusCode < 200 || response.statusCode > 299) {
bot.error(response, 'Failed to load page, status code: ' + response.statusCode);
reject(new Error('Failed to load page, status code: ' + response.statusCode));
bot.error(response, `Failed to load page, status code: ${response.statusCode}`)
reject(new Error(`Failed to load page, status code: ${response.statusCode}`))
}
// temporary data holder
const body = [];
const body = []
// on every content chunk, push it to the data array
response.on('data', (chunk) => body.push(chunk));
response.on('data', (chunk) => body.push(chunk))
// we are done, resolve promise with those joined chunks
response.on('end', () => {
const labels = JSON.parse(body.join('')).map(labelObj => labelObj.name);
resolve(labels);
});
});
const labels = JSON.parse(body.join('')).map(labelObj => labelObj.name)
resolve(labels)
})
})
// handle connection errors of the request
request.on('error', (err) => reject(err))
});
}
})
}
module.exports = {

View File

@ -1,17 +1,13 @@
const winston = require('winston');
const winston = require('winston')
const ethers = require('ethers');
const Wallet = ethers.Wallet;
const Contract = ethers.Contract;
const providers = ethers.providers;
const utils = ethers.utils;
const ethers = require('ethers')
const { Wallet, Contract, providers } = ethers
const prices = require('./prices');
const config = require('../config');
const github = require('./github');
const contractAddressString = 'Contract address:';
const config = require('../config')
const prices = require('./prices')
const github = require('./github')
const contractAddressString = 'Contract address:'
const logger = winston.createLogger({
level: 'info',
@ -19,136 +15,139 @@ const logger = winston.createLogger({
transports: [
new winston.transports.File({ filename: './log/error.log', level: 'error' }),
new winston.transports.File({ filename: './log/info.log', level: 'info' }),
// new winston.transports.File({ filename: './log/combined.log' })
new winston.transports.Console({
format: winston.format.simple(),
level: 'debug',
colorize: true,
stderrLevels: ['error', 'debug', 'info'],
silent: process.env.NODE_ENV === 'production'
})
]
});
})
const needsFunding = function (req) {
function needsFunding (req) {
if (req.body.action !== 'edited' || !req.body.hasOwnProperty('comment')) {
return false
} else if (req.body.comment.user.login !== config.githubUsername) {
return false
} else if (!hasAddress(req)) {
return false;
return false
}
return true
}
const hasAddress = function (req) {
const commentBody = req.body.comment.body;
if (commentBody.search(contractAddressString) === -1) {
return false;
} else {
return true;
}
function hasAddress (req) {
return req.body.comment.body.search(contractAddressString) !== -1
}
const getAddress = function (req) {
const commentBody = req.body.comment.body;
function getAddress (req) {
const commentBody = req.body.comment.body
const index = commentBody.search(contractAddressString) + 19
return commentBody.substring(index, index + 42)
}
const getLabel = function (req) {
return new Promise((resolve, reject) => {
github.getLabels(req)
.then(labels => {
const bountyLabels = labels.filter(label => config.bountyLabels.hasOwnProperty(label.name));
async function getLabel (req) {
const labelNames = await github.getLabels(req)
const bountyLabels = labelNames.filter(labelName => config.bountyLabels.hasOwnProperty(labelName))
if (bountyLabels.length === 1) {
resolve(bountyLabels[0]);
} else {
reject('Error getting bounty labels');
}
}).catch(err => {
reject(err);
});
});
return bountyLabels[0]
}
const getAmountMock = function (req) {
return new Promise((resolve, reject) => {
resolve(10);
});
throw new Error('Error getting bounty labels')
}
const getAmount = function (req) {
return new Promise((resolve, reject) => {
const labelPromise = getLabel(req);
const tokenPricePromise = prices.getTokenPrice(config.token);
async function getAmount (req) {
const labelName = await getLabel(req)
const tokenPrice = await prices.getTokenPrice(config.token)
Promise.all([labelPromise, tokenPricePromise])
.then(function (values) {
let label = values[0];
let tokenPrice = values[1];
let amountToPayDollar = config.priceHour * config.bountyLabels[label.name];
resolve(amountToPayDollar / tokenPrice);
})
.catch((err) => {
reject(err);
});
});
const bountyLabelHours = config.bountyLabels[labelName]
if (!bountyLabelHours) {
throw new Error(`Label '${labelName}' not found in config`)
}
const amountToPayDollar = config.priceHour * bountyLabelHours
return (amountToPayDollar / tokenPrice)
}
// Logging functions
const logTransaction = function (tx) {
logger.info("[OK] Succesfully funded bounty with transaction " + tx.hash);
logger.info(" * From: " + tx.from);
logger.info(" * To: " + tx.to);
logger.info(" * Amount: " + tx.value);
logger.info(" * Gas Price: " + tx.gasPrice);
logger.info("====================================================");
function logTransaction (tx) {
info(`[OK] Succesfully funded bounty with transaction ${tx.hash}`)
info(` * From: ${tx.from}`)
info(` * To: ${tx.to}`)
info(` * Amount: ${tx.value}`)
info(` * Gas Price: ${tx.gasPrice}`)
info(`====================================================`)
}
const info = function (msg) {
logger.info(msg);
function info (msg) {
logger.info(msg)
}
const error = function (errorMessage) {
logger.error("[ERROR] Request processing failed: " + errorMessage);
function error (errorMessage) {
logger.error(`[ERROR] Request processing failed: ${errorMessage}`)
}
const sendTransaction = async function (to, amount, gasPrice) {
var chainId = providers.Provider.chainId.ropsten;
var chainName = providers.networks.ropsten;
if (config.realTransaction) {
chainId = providers.Provider.chainId.homestead;
chainName = providers.networks.homestead;
async function sendTransaction (to, amount, gasPrice) {
if (isNaN(amount)) {
throw Error('Invalid amount')
}
if (!config.privateKey.startsWith('0x')) {
throw Error('Private key should start with 0x')
}
const wallet = new Wallet(config.privateKey);
const provider = ethers.providers.getDefaultProvider(chainName);
let transaction = null
let hash = null
const network = providers.Provider.getNetwork(config.realTransaction ? 'homestead' : 'ropsten')
const wallet = new Wallet(config.privateKey)
wallet.provider = ethers.providers.getDefaultProvider(network)
async function customSendTransaction (tx) {
hash = await wallet.provider.sendTransaction(tx)
return hash
}
async function customSignTransaction (tx) {
transaction = tx
return wallet.sign(tx)
}
wallet.provider = provider;
if (config.token === 'ETH') {
const transaction = {
gasLimit: config.gasLimit,
gasPrice: gasPrice,
to: to,
value: amount,
chainId: chainId
};
return await wallet.sendTransaction(transaction);
} else {
let hash = null;
async function getAddress() { return wallet.address; }
async function sign(transaction) { return wallet.sign(transaction); }
async function resolveName(addressOrName) { return await provider.resolveName(addressOrName); }
async function estimateGas(transaction) { return await provider.estimateGas(transaction); }
async function getGasPrice() { return await provider.getGasPrice(); }
async function getTransactionCount(blockTag) { return await provider.getTransactionCount(blockTag); }
async function sendTransaction(transaction) {
hash = await provider.sendTransaction(transaction);
return hash;
chainId: network.chainId
}
await wallet.sendTransaction(transaction)
} else {
const customSigner = getCustomSigner(wallet, customSignTransaction, customSendTransaction)
const tokenContract = config.tokenContracts[config.token]
const contractAddress = tokenContract.address
const contract = new Contract(contractAddress, tokenContract.abi, customSigner)
const bigNumberAmount = ethers.utils.parseUnits(amount.toString(), 'ether')
await contract.transfer(to, bigNumberAmount)
transaction.hash = hash
transaction.from = wallet.address
transaction.value = bigNumberAmount
}
return transaction
}
function getCustomSigner (wallet, signTransaction, sendTransaction) {
const provider = wallet.provider
async function getAddress () { return wallet.address }
async function resolveName (addressOrName) { return provider.resolveName(addressOrName) }
async function estimateGas (transaction) { return provider.estimateGas(transaction) }
async function getGasPrice () { return provider.getGasPrice() }
async function getTransactionCount (blockTag) { return provider.getTransactionCount(blockTag) }
const customSigner = {
getAddress: getAddress,
provider: {
@ -158,17 +157,10 @@ const sendTransaction = async function (to, amount, gasPrice) {
getTransactionCount: getTransactionCount,
sendTransaction: sendTransaction
},
sign: sign
sign: signTransaction
}
const tokenContract = config.tokenContracts[config.token];
const contractAddress = tokenContract.address;
const contract = new Contract(contractAddress, tokenContract.abi, customSigner);
const bigNumberAmount = ethers.utils.bigNumberify(amount);
await contract.transfer(to, bigNumberAmount);
return hash;
}
return customSigner
}
module.exports = {

View File

@ -1,67 +1,60 @@
"use strict"
'use strict'
const https = require("https");
const config = require("../config");
const getGasPrice = function () {
const url = 'https://ethgasstation.info/json/ethgasAPI.json';
function getGasPrice () {
const url = 'https://ethgasstation.info/json/ethgasAPI.json'
// return new pending promise
return new Promise((resolve, reject) => {
// select http or https module, depending on reqested url
const lib = url.startsWith('https') ? require('https') : require('http');
const lib = url.startsWith('https') ? require('https') : require('http')
const request = lib.get(url, (response) => {
// handle http errors
if (response.statusCode < 200 || response.statusCode > 299) {
reject('Failed to load page, status code: ' + response.statusCode);
reject(new Error(`Failed to load page, status code: ${response.statusCode}`))
}
// temporary data holder
const body = [];
const body = []
// on every content chunk, push it to the data array
response.on('data', (chunk) => body.push(chunk));
response.on('data', (chunk) => body.push(chunk))
// we are done, resolve promise with those joined chunks
response.on('end', () => {
// safeLowWait returns GWei (10^10 Wei).
let jsonBody = JSON.parse(body.join(''));
let gasPriceWei = parseInt(jsonBody['safeLowWait']) * Math.pow(10, 10);
resolve(gasPriceWei);
});
});
// handle connection errors of the request
request.on('error', (err) => reject(err));
const jsonBody = JSON.parse(body.join(''))
const gasPriceWei = parseInt(jsonBody['safeLowWait']) * Math.pow(10, 10)
resolve(gasPriceWei)
})
})
// handle connection errors of the request
request.on('error', (err) => reject(err))
})
};
const getTokenPriceMock = function () {
return new Promise((resolve, reject) => resolve(0.35));
}
const getTokenPrice = function (token) {
if (config.debug) {
return getTokenPriceMock();
function getTokenPrice (token) {
if (token === 'STT') {
return 1
}
const currency = 'USD'
const url = 'https://min-api.cryptocompare.com/data/price?fsym=' + token + '&tsyms=' + currency;
const url = `https://min-api.cryptocompare.com/data/price?fsym=${token}&tsyms=${currency}`
// return new pending promise
return new Promise((resolve, reject) => {
// select http or https module, depending on reqested url
const lib = url.startsWith('https') ? require('https') : require('http');
const lib = url.startsWith('https') ? require('https') : require('http')
const request = lib.get(url, (response) => {
// handle http errors
if (response.statusCode < 200 || response.statusCode > 299) {
reject('Failed to load page, status code: ' + response.statusCode);
reject(new Error(`Failed to load page, status code: ${response.statusCode}`))
}
// temporary data holder
const body = [];
const body = []
// on every content chunk, push it to the data array
response.on('data', (chunk) => body.push(chunk));
response.on('data', (chunk) => body.push(chunk))
// we are done, resolve promise with those joined chunks
response.on('end', () => {
let jsonBody = JSON.parse(body.join(''));
let tokenPrice = parseFloat(jsonBody[currency]);
resolve(tokenPrice);
});
});
const jsonBody = JSON.parse(body.join(''))
const tokenPrice = parseFloat(jsonBody[currency])
resolve(tokenPrice)
})
})
// handle connection errors of the request
request.on('error', (err) => reject(err))
})

View File

@ -9,28 +9,28 @@ const BOUNTY_LABELS = {
const ERC20_ABI = [
{
"constant": false,
"inputs": [
'constant': false,
'inputs': [
{
"name": "_to",
"type": "address"
'name': '_to',
'type': 'address'
},
{
"name": "_amount",
"type": "uint256"
'name': '_amount',
'type': 'uint256'
}
],
"name": "transfer",
"outputs": [
'name': 'transfer',
'outputs': [
{
"name": "success",
"type": "bool"
'name': 'success',
'type': 'bool'
}
],
"payable": false,
"type": "function"
'payable': false,
'type': 'function'
}
];
]
const TOKEN_CONTRACTS = {
'STT': {
@ -53,14 +53,14 @@ module.exports = {
// URL for the signer
signerPath: 'https://ropsten.infura.io',
// Address with the funding for the bounties
// Address with the funding for the bounties (hex value starting with 0x)
sourceAddress: '0x26a4D114B98C4b0B0118426F10fCc1112AA2864d',
// Private key for ether.js wallet
// Private key for ether.js wallet (hex value starting with 0x)
privateKey: '',
// Token of the currency for fetching real time prices (e.g. 'SNT')
token: 'SNT',
token: 'STT',
// Limit for the gas used in a transaction (e.g. 92000)
gasLimit: 92000,

View File

@ -1,4 +1,5 @@
var _ = require("lodash");
var defaults = require("./default.js");
var config = require("./" + (process.env.NODE_ENV || "default") + ".js");
module.exports = _.merge({}, defaults, config);
const _ = require('lodash')
const defaults = require('./default.js')
const configFileName = process.env.NODE_ENV || 'default'
const config = require(`./${configFileName}.js`)
module.exports = _.merge({}, defaults, config)

130
index.js
View File

@ -7,114 +7,88 @@
* Depends on commiteth version as of 2017-06-10.
*/
const config = require('./config');
const bot = require('./bot');
const crypto = require('crypto');
const config = require('./config')
const bot = require('./bot')
const crypto = require('crypto')
const express = require('express')
const cors = require('cors')
const helmet = require('helmet')
const app = express()
const bodyParser = require('body-parser')
const jsonParser = bodyParser.json()
var express = require('express'),
cors = require('cors'),
helmet = require('helmet'),
app = express(),
bodyParser = require('body-parser'),
jsonParser = bodyParser.json();
app.use(cors());
app.use(helmet());
app.use(cors())
app.use(helmet())
// Receive a POST request at the url specified by an env. var.
app.post(`${config.urlEndpoint}`, jsonParser, function (req, res, next) {
if (!req.body || !req.body.action) {
return res.sendStatus(400);
return res.sendStatus(400)
} else if (!bot.needsFunding(req)) {
return res.sendStatus(204);
return res.sendStatus(204)
}
validation = validateRequest(req);
const validation = validateRequest(req)
if (validation.correct) {
setTimeout(() => {
processRequest(req)
.then(() => {
bot.info('issue well funded: ' + req.body.issue.url);
})
.catch((err) => {
bot.error('Error processing request: ' + req.body.issue.url);
bot.error('Error: ' + err);
bot.error('Dump: ', req.body);
});
}, config.delayInMiliSeconds);
} else {
bot.error('Error validating issue: ' + req.body.issue.url);
bot.error('Error: ' + validation.error);
setTimeout(async () => {
try {
await processRequest(req)
bot.info(`issue well funded: ${req.body.issue.url}`)
} catch (err) {
bot.error(`Error processing request: ${req.body.issue.url}`)
bot.error(err)
bot.error(`Dump: ${JSON.stringify(req.body)}`)
}
return res.sendStatus(200);
});
}, config.delayInMiliSeconds)
} else {
bot.error(`Error validating issue: ${req.body.issue.url}`)
bot.error(`Error: ${validation.error}`)
}
return res.sendStatus(200)
})
const validateRequest = function (req) {
validation = {correct: false, error: ''};
webhookSecret = process.env.WEBHOOK_SECRET;
function validateRequest (req) {
const validation = { correct: false, error: '' }
const webhookSecret = process.env.WEBHOOK_SECRET
if (!webhookSecret) {
validation.error = 'Github Webhook Secret key not found. ' +
'Please set env variable WEBHOOK_SECRET to github\'s webhook secret value';
'Please set env variable WEBHOOK_SECRET to github\'s webhook secret value'
} else {
const blob = JSON.stringify(req.body)
const hmac = crypto.createHmac('sha1', webhookSecret)
const ourSignature = `sha1=${hmac.update(blob).digest('hex')}`
const blob = JSON.stringify(req.body);
const hmac = crypto.createHmac('sha1', webhookSecret);
const ourSignature = `sha1=${hmac.update(blob).digest('hex')}`;
const theirSignature = req.get('X-Hub-Signature')
const theirSignature = req.get('X-Hub-Signature');
const bufferA = Buffer.from(ourSignature, 'utf8')
const bufferB = Buffer.from(theirSignature, 'utf8')
const bufferA = Buffer.from(ourSignature, 'utf8');
const bufferB = Buffer.from(theirSignature, 'utf8');
const safe = crypto.timingSafeEqual(bufferA, bufferB);
const safe = crypto.timingSafeEqual(bufferA, bufferB)
if (safe) {
validation.correct = true;
validation.correct = true
} else {
validation.error = 'Invalid signature. Check that WEBHOOK_SECRET ' +
'env variable matches github\'s webhook secret value';
'env variable matches github\'s webhook secret value'
}
}
return validation;
return validation
}
const processRequest = function (req) {
// const wallet = bot.wallet;
async function processRequest (req) {
const to = bot.getAddress(req)
const amount = await bot.getAmount(req)
const gasPrice = await bot.getGasPrice()
const hash = await bot.sendTransaction(to, amount, gasPrice)
const from = config.sourceAddress;
const to = bot.getAddress(req);
// Asynchronous requests for Gas Price and Amount
const amountPromise = bot.getAmount(req);
const gasPricePromise = bot.getGasPrice();
return new Promise((resolve, reject) => {
Promise.all([amountPromise, gasPricePromise])
.then(function (results) {
const amount = results[0];
const gasPrice = results[1];
bot.sendTransaction(to, amount, gasPrice)
.then(function (hash) {
bot.logTransaction(hash);
resolve();
})
.catch(function (err) {
reject(err);
});
})
.catch(function (err) {
reject(err);
});
});
bot.logTransaction(hash)
}
const port = process.env.PORT || 8181
app.listen(port, function () {
bot.info('Autobounty listening on port', port);
});
bot.info('Autobounty listening on port', port)
})

2504
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -12,18 +12,25 @@
"license": "ISC",
"dependencies": {
"body-parser": "^1.17.2",
"chai": "^4.1.2",
"cors": "^2.8.1",
"eslint": "^4.15.0",
"ethers": "^2.2.6",
"ethjs-provider-signer": "^0.1.4",
"ethjs-query": "^0.2.4",
"ethjs-signer": "^0.1.1",
"ethers": "^3.0",
"ethjs-provider-signer": "^0.1",
"ethjs-query": "^0.2",
"ethjs-signer": "^0.1",
"express": "^4.15.2",
"helmet": "^3.9.0",
"lodash": "^4.17.4",
"mocha": "^5.0.0",
"web3": "^0.18.2",
"winston": "^3.0.0-rc1"
"winston": "^3.0.0-rc3"
},
"devDependencies": {
"chai": "^4.1.2",
"eslint": "^4.19.0",
"eslint-config-standard": "^11.0.0",
"eslint-plugin-import": "^2.9.0",
"eslint-plugin-node": "^6.0.1",
"eslint-plugin-promise": "^3.7.0",
"eslint-plugin-standard": "^3.0.1",
"mocha": "^5.0.4"
}
}

View File

@ -1,56 +1,55 @@
const chai = require('chai');
const expect = require('chai').expect;
const assert = require('chai').assert;
const should = require('chai').should;
const chai = require('chai')
const expect = require('chai').expect
const assert = require('chai').assert
const should = require('chai').should
const config = require('../config')
const bot = require('../bot')
// status-open-bounty comment from https://github.com/status-im/autobounty/issues/1
let sob_comment = 'Current balance: 0.000000 ETH\nTokens: SNT: 2500.00 ANT: 25.00\nContract address: 0x3645fe42b1a744ad98cc032c22472388806f86f9\nNetwork: Mainnet\n To claim this bounty sign up at https://openbounty.status.im and make sure to update your Ethereum address in My Payment Details so that the bounty is correctly allocated.\nTo fund it, send ETH or ERC20/ERC223 tokens to the contract address.'
const sobComment = 'Current balance: 0.000000 ETH\nTokens: SNT: 2500.00 ANT: 25.00\nContract address: 0x3645fe42b1a744ad98cc032c22472388806f86f9\nNetwork: Mainnet\n To claim this bounty sign up at https://openbounty.status.im and make sure to update your Ethereum address in My Payment Details so that the bounty is correctly allocated.\nTo fund it, send ETH or ERC20/ERC223 tokens to the contract address.'
// Fake requests
let requests = [
const requests = [
{ body: { action: 'created', comment: { body: 'Creating my first comment', user: { login: 'randomUser' } } } },
{ body: { action: 'edited', comment: { body: 'Editing my comment', user: { login: 'RandomUser' } } } },
{ body: { action: 'edited', comment: { body: sob_comment, user: { login: 'status-open-bounty' } } } },
{ body: { action: 'created', issue: { labels: ['bounty', 'bounty-s'] }, comment: { body: sob_comment, user: { login: 'status-open-bounty' } } } }
];
{ body: { action: 'edited', comment: { body: sobComment, user: { login: 'status-open-bounty' } } } },
{ body: { action: 'created', issue: { labels: ['bounty', 'bounty-s'] }, comment: { body: sobComment, user: { login: 'status-open-bounty' } } } }
]
describe('Bot behavior', function () {
describe('#needsFunding()', function () {
it('should return false because the comment is not from status-open-bounty', function () {
assert.isFalse(bot.needsFunding(requests[0]));
});
assert.isFalse(bot.needsFunding(requests[0]))
})
it('should return false because a user is editing a comment', function () {
assert.isFalse(bot.needsFunding(requests[1]));
});
assert.isFalse(bot.needsFunding(requests[1]))
})
it('should return false because status-open-bounty edited a comment', function () {
assert.isFalse(bot.needsFunding(requests[2]));
});
assert.isFalse(bot.needsFunding(requests[2]))
})
it('should return true, it is all right and we should fund', function () {
assert.isTrue(bot.needsFunding(requests[3]));
});
});
assert.isTrue(bot.needsFunding(requests[3]))
})
})
describe('#getAddress', function () {
it('should return the address from a status-open-bounty bot comment', function () {
assert.equal(bot.getAddress(requests[3]), '0x3645fe42b1a744ad98cc032c22472388806f86f9');
});
});
assert.equal(bot.getAddress(requests[3]), '0x3645fe42b1a744ad98cc032c22472388806f86f9')
})
})
describe('#getAmount', function () {
it('should return the amount for the issue given the price per hour and the bounty label for this issue', (done) => {
bot.getAmount(requests[3])
.then(function (amount) {
let label = 'bounty-s';
let tokenPrice = 0.35;
let priceInDollars = config.priceHour * config.bountyLabels[label];
expected_amount = priceInDollars / tokenPrice;
assert.equal(amount, expected_amount);
done();
const label = 'bounty-s'
const tokenPrice = 0.35
const priceInDollars = config.priceHour * config.bountyLabels[label]
expected_amount = priceInDollars / tokenPrice
assert.equal(amount, expected_amount)
done()
})
.catch(() => { console.log('error'), done() })
})
})
})
.catch(() => { console.log('error'), done() });
});
});
});