Add ESLint and convert code to ES6

This commit is contained in:
Pedro Pombeiro 2018-03-19 23:02:24 +01:00
parent 83df18e9d8
commit 434f8c9cf5
No known key found for this signature in database
GPG Key ID: A65DEB11E4BBC647
9 changed files with 371 additions and 427 deletions

3
.eslintrc.json Normal file
View File

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

View File

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

View File

@ -4,180 +4,155 @@ const ethers = require('ethers')
const Wallet = ethers.Wallet const Wallet = ethers.Wallet
const Contract = ethers.Contract const Contract = ethers.Contract
const providers = ethers.providers const providers = ethers.providers
const utils = ethers.utils
const prices = require('./prices')
const config = require('../config') const config = require('../config')
const prices = require('./prices')
const github = require('./github') const github = require('./github')
const contractAddressString = 'Contract address:' const contractAddressString = 'Contract address:'
const logger = winston.createLogger({ const logger = winston.createLogger({
level: 'info', level: 'info',
format: winston.format.json(), format: winston.format.json(),
transports: [ transports: [
new winston.transports.File({ filename: './log/error.log', level: 'error' }), 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/info.log', level: 'info' })
// new winston.transports.File({ filename: './log/combined.log' }) // new winston.transports.File({ filename: './log/combined.log' })
] ]
}) })
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 true
}
const needsFunding = function (req) { function hasAddress (req) {
if (req.body.action !== 'edited' || !req.body.hasOwnProperty('comment')) { const commentBody = req.body.comment.body
return false if (commentBody.search(contractAddressString) === -1) {
} else if (req.body.comment.user.login !== config.githubUsername) { return false
return false } else {
} else if (!hasAddress(req)) {
return false
}
return true return true
}
} }
const hasAddress = function (req) { function getAddress (req) {
const commentBody = req.body.comment.body const commentBody = req.body.comment.body
if (commentBody.search(contractAddressString) === -1) { const index = commentBody.search(contractAddressString) + 19
return false return commentBody.substring(index, index + 42)
} else {
return true
}
} }
const getAddress = function (req) { async function getLabel (req) {
const commentBody = req.body.comment.body const labels = await github.getLabels(req)
const index = commentBody.search(contractAddressString) + 19 const bountyLabels = labels.filter(label => config.bountyLabels.hasOwnProperty(label.name))
return commentBody.substring(index, index + 42) if (bountyLabels.length === 1) {
return bountyLabels[0]
} else {
throw new Error('Error getting bounty labels')
}
} }
const getLabel = function (req) { async function getAmount (req) {
return new Promise((resolve, reject) => { const label = await getLabel(req)
github.getLabels(req) const tokenPrice = await prices.getTokenPrice(config.token)
.then(labels => {
const bountyLabels = labels.filter(label => config.bountyLabels.hasOwnProperty(label.name)) const amountToPayDollar = config.priceHour * config.bountyLabels[label.name]
if (bountyLabels.length === 1) { return (amountToPayDollar / tokenPrice)
resolve(bountyLabels[0])
} else {
reject('Error getting bounty labels')
}
}).catch(err => {
reject(err)
})
})
} }
const getAmountMock = function (req) {
return new Promise((resolve, reject) => {
resolve(10)
})
}
const getAmount = function (req) {
return new Promise((resolve, reject) => {
const labelPromise = getLabel(req)
const tokenPricePromise = prices.getTokenPrice(config.token)
Promise.all([labelPromise, tokenPricePromise])
.then(function (values) {
const label = values[0]
const tokenPrice = values[1]
const amountToPayDollar = config.priceHour * config.bountyLabels[label.name]
resolve(amountToPayDollar / tokenPrice)
})
.catch((err) => {
reject(err)
})
})
}
// Logging functions // Logging functions
const logTransaction = function (tx) { function logTransaction (tx) {
logger.info("[OK] Succesfully funded bounty with transaction " + tx.hash) logger.info(`[OK] Succesfully funded bounty with transaction ${tx.hash}`)
logger.info(" * From: " + tx.from) logger.info(` * From: ${tx.from}`)
logger.info(" * To: " + tx.to) logger.info(` * To: ${tx.to}`)
logger.info(" * Amount: " + tx.value) logger.info(` * Amount: ${tx.value}`)
logger.info(" * Gas Price: " + tx.gasPrice) logger.info(` * Gas Price: ${tx.gasPrice}`)
logger.info("====================================================") logger.info(`====================================================`)
} }
const info = function (msg) { function info (msg) {
logger.info(msg) logger.info(msg)
} }
const error = function (errorMessage) { function error (errorMessage) {
logger.error("[ERROR] Request processing failed: " + errorMessage) logger.error(`[ERROR] Request processing failed: ${errorMessage}`)
} }
async function sendTransaction (to, amount, gasPrice) {
let hash = null
let chainId = providers.Provider.chainId.ropsten
let chainName = providers.networks.ropsten
const sendTransaction = async function (to, amount, gasPrice) { if (config.realTransaction) {
let chainId = providers.Provider.chainId.ropsten chainId = providers.Provider.chainId.homestead
let chainName = providers.networks.ropsten chainName = providers.networks.homestead
}
if (config.realTransaction) { const wallet = new Wallet(config.privateKey)
chainId = providers.Provider.chainId.homestead wallet.provider = ethers.providers.getDefaultProvider(chainName)
chainName = providers.networks.homestead
if (config.token === 'ETH') {
const transaction = {
gasLimit: config.gasLimit,
gasPrice: gasPrice,
to: to,
value: amount,
chainId: chainId
} }
const wallet = new Wallet(config.privateKey) hash = await wallet.sendTransaction(transaction)
const provider = ethers.providers.getDefaultProvider(chainName) } else {
const customSigner = getCustomSigner(wallet, sendTransaction)
wallet.provider = provider const tokenContract = config.tokenContracts[config.token]
if (config.token === 'ETH') { const contractAddress = tokenContract.address
const transaction = { const contract = new Contract(contractAddress, tokenContract.abi, customSigner)
gasLimit: config.gasLimit, const bigNumberAmount = ethers.utils.bigNumberify(amount)
gasPrice: gasPrice,
to: to,
value: amount,
chainId: chainId
}
return await wallet.sendTransaction(transaction) await contract.transfer(to, bigNumberAmount)
} else { }
let hash = null
async function getAddress() { return wallet.address } return hash
async function sign(transaction) { return wallet.sign(transaction) } }
async function resolveName(addressOrName) { return await provider.resolveName(addressOrName) } function getCustomSigner (wallet, sendTransaction) {
async function estimateGas(transaction) { return await provider.estimateGas(transaction) } const provider = wallet.provider
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
}
const customSigner = {
getAddress: getAddress,
provider: {
resolveName: resolveName,
estimateGas: estimateGas,
getGasPrice: getGasPrice,
getTransactionCount: getTransactionCount,
sendTransaction: sendTransaction
},
sign: sign
}
const tokenContract = config.tokenContracts[config.token] async function getAddress () { return wallet.address }
const contractAddress = tokenContract.address async function sign (transaction) { return wallet.sign(transaction) }
const contract = new Contract(contractAddress, tokenContract.abi, customSigner)
const bigNumberAmount = ethers.utils.bigNumberify(amount)
await contract.transfer(to, bigNumberAmount)
return hash 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: {
resolveName: resolveName,
estimateGas: estimateGas,
getGasPrice: getGasPrice,
getTransactionCount: getTransactionCount,
sendTransaction: sendTransaction
},
sign: sign
}
return customSigner
} }
module.exports = { module.exports = {
needsFunding: needsFunding, needsFunding: needsFunding,
getAddress: getAddress, getAddress: getAddress,
getAmount: getAmount, getAmount: getAmount,
getGasPrice: prices.getGasPrice, getGasPrice: prices.getGasPrice,
sendTransaction: sendTransaction, sendTransaction: sendTransaction,
info: info, info: info,
logTransaction: logTransaction, logTransaction: logTransaction,
error: error error: error
} }

View File

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

View File

@ -1,85 +1,85 @@
// Work hours per label // Work hours per label
const BOUNTY_LABELS = { const BOUNTY_LABELS = {
'bounty-xs': 1, 'bounty-xs': 1,
'bounty-s': 10, 'bounty-s': 10,
'bounty-m': 100, 'bounty-m': 100,
'bounty-l': 1000, 'bounty-l': 1000,
'bounty-xl': 10000 'bounty-xl': 10000
} }
const ERC20_ABI = [ const ERC20_ABI = [
{ {
"constant": false, 'constant': false,
"inputs": [ 'inputs': [
{ {
"name": "_to", 'name': '_to',
"type": "address" 'type': 'address'
}, },
{ {
"name": "_amount", 'name': '_amount',
"type": "uint256" 'type': 'uint256'
} }
], ],
"name": "transfer", 'name': 'transfer',
"outputs": [ 'outputs': [
{ {
"name": "success", 'name': 'success',
"type": "bool" 'type': 'bool'
} }
], ],
"payable": false, 'payable': false,
"type": "function" 'type': 'function'
} }
] ]
const TOKEN_CONTRACTS = { const TOKEN_CONTRACTS = {
'STT': { 'STT': {
address: '0xc55cF4B03948D7EBc8b9E8BAD92643703811d162', address: '0xc55cF4B03948D7EBc8b9E8BAD92643703811d162',
abi: ERC20_ABI abi: ERC20_ABI
}, },
'SNT': { 'SNT': {
address: '0x744d70fdbe2ba4cf95131626614a1763df805b9e', address: '0x744d70fdbe2ba4cf95131626614a1763df805b9e',
abi: ERC20_ABI abi: ERC20_ABI
} }
} }
module.exports = { module.exports = {
// Debug mode for testing the bot // Debug mode for testing the bot
debug: true, debug: true,
// URL where the bot is listening (e.g. '/funding') // URL where the bot is listening (e.g. '/funding')
urlEndpoint: '/', urlEndpoint: '/',
// URL for the signer // URL for the signer
signerPath: 'https://ropsten.infura.io', signerPath: 'https://ropsten.infura.io',
// Address with the funding for the bounties // Address with the funding for the bounties
sourceAddress: '0x26a4D114B98C4b0B0118426F10fCc1112AA2864d', sourceAddress: '0x26a4D114B98C4b0B0118426F10fCc1112AA2864d',
// Private key for ether.js wallet // Private key for ether.js wallet
privateKey: '', privateKey: '',
// Token of the currency for fetching real time prices (e.g. 'SNT') // Token of the currency for fetching real time prices (e.g. 'SNT')
token: 'SNT', token: 'SNT',
// Limit for the gas used in a transaction (e.g. 92000) // Limit for the gas used in a transaction (e.g. 92000)
gasLimit: 92000, gasLimit: 92000,
// Price per hour you will pay in dolars (e.g. 35) // Price per hour you will pay in dolars (e.g. 35)
priceHour: 1, priceHour: 1,
// Delay before funding a bounty (e.g. 3600000) // Delay before funding a bounty (e.g. 3600000)
delayInMiliSeconds: 10000, delayInMiliSeconds: 10000,
// Bounty Labels for the issues and the correspondent hours (e.g. {'bounty-xs': 3}) // Bounty Labels for the issues and the correspondent hours (e.g. {'bounty-xs': 3})
bountyLabels: BOUNTY_LABELS, bountyLabels: BOUNTY_LABELS,
// Contract info for the different supported tokens // Contract info for the different supported tokens
tokenContracts: TOKEN_CONTRACTS, tokenContracts: TOKEN_CONTRACTS,
// username for the bot which has to comment for starting the process (e.g. status-bounty-) // username for the bot which has to comment for starting the process (e.g. status-bounty-)
githubUsername: 'status-open-bounty', githubUsername: 'status-open-bounty',
// Activate real transactions // Activate real transactions
realTransaction: false realTransaction: false
} }

View File

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

145
index.js
View File

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

View File

@ -12,9 +12,7 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"body-parser": "^1.17.2", "body-parser": "^1.17.2",
"chai": "^4.1.2",
"cors": "^2.8.1", "cors": "^2.8.1",
"eslint": "^4.15.0",
"ethers": "^2.2.6", "ethers": "^2.2.6",
"ethjs-provider-signer": "^0.1.4", "ethjs-provider-signer": "^0.1.4",
"ethjs-query": "^0.2.4", "ethjs-query": "^0.2.4",
@ -22,8 +20,17 @@
"express": "^4.15.2", "express": "^4.15.2",
"helmet": "^3.9.0", "helmet": "^3.9.0",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"mocha": "^5.0.0",
"web3": "^0.18.2", "web3": "^0.18.2",
"winston": "^3.0.0-rc1" "winston": "^3.0.0-rc1"
},
"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

@ -5,52 +5,51 @@ const should = require('chai').should
const config = require('../config') const config = require('../config')
const bot = require('../bot') const bot = require('../bot')
// status-open-bounty comment from https://github.com/status-im/autobounty/issues/1 // status-open-bounty comment from https://github.com/status-im/autobounty/issues/1
const 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 // Fake requests
const requests = [ const requests = [
{ body: { action: 'created', comment: { body: 'Creating my first comment', user: { login: 'randomUser' } } } }, { 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: 'Editing my comment', user: { login: 'RandomUser' } } } },
{ body: { action: 'edited', 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: sob_comment, 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('Bot behavior', function () {
describe('#needsFunding()', function () { describe('#needsFunding()', function () {
it('should return false because the comment is not from status-open-bounty', 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]))
})
it('should return false because status-open-bounty edited a comment', function () {
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]))
})
}) })
it('should return false because a user is editing a comment', function () {
assert.isFalse(bot.needsFunding(requests[1]))
})
it('should return false because status-open-bounty edited a comment', function () {
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]))
})
})
describe('#getAddress', function () { describe('#getAddress', function () {
it('should return the address from a status-open-bounty bot comment', 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 () { describe('#getAmount', function () {
it('should return the amount for the issue given the price per hour and the bounty label for this issue', (done) => { 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]) bot.getAmount(requests[3])
.then(function (amount) { .then(function (amount) {
const label = 'bounty-s' const label = 'bounty-s'
const tokenPrice = 0.35 const tokenPrice = 0.35
const priceInDollars = config.priceHour * config.bountyLabels[label] const priceInDollars = config.priceHour * config.bountyLabels[label]
expected_amount = priceInDollars / tokenPrice expected_amount = priceInDollars / tokenPrice
assert.equal(amount, expected_amount) assert.equal(amount, expected_amount)
done() done()
})
.catch(() => { console.log('error'), done() })
}) })
.catch(() => { console.log('error'), done() })
}) })
})
}) })