Skip to content

Commit

Permalink
WIP: metadataDAOEvaluate
Browse files Browse the repository at this point in the history
  • Loading branch information
izqui committed Feb 14, 2019
1 parent 2725fed commit 11de4b1
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 2 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"coveralls": "^3.0.0",
"documentation": "^9.0.0-alpha.0",
"nyc": "^11.3.0",
"standard": "^12.0.1"
"standard": "^12.0.1",
"yargs": "^13.1.0"
},
"dependencies": {
"@babel/runtime": "^7.1.2",
Expand Down
55 changes: 55 additions & 0 deletions scripts/inspect
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env node

const yargs = require('yargs')
const fs = require('fs')
const path = require('path')
const Eth = require('web3-eth')

const { metadataDAOEvaluate } = require('../dist')

const NETWORKS = {
rinkeby: 'https://rinkeby.eth.aragon.network',
mainnet: 'https://mainnet.eth.aragon.network',
}

const exec = async (argv) => {
const { network } = argv
const [ txHash ] = argv._

const rpc = NETWORKS[network]

if (!rpc) {
throw new Error(`Unsupported network '${network}'`)
}

const eth = new Eth(rpc)

console.log(`Fetching ${network} for ${txHash}`)
const {
to,
from,
blockNumber,
input: data
} = await eth.getTransaction(txHash)

const description = await metadataDAOEvaluate({ transaction: { to, data }})

console.log(`Transaction from ${from} to ${to} in block ${blockNumber}:\n`)
console.log(description ? `🔥 ${description} 🔥` : 'Unknown 😢')
}

exec(
yargs
.usage('Usage: $0 [txid]')
.option('network', {
alias: 'n',
default: 'mainnet',
describe: 'Output path to radspec db file',
type: 'string',
})
.argv
)
.catch(err => {
console.error(err)
process.exit(1)
})
91 changes: 91 additions & 0 deletions src/extract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
const fs = require('fs')
const { promisify } = require('util')
const readFile = promisify(fs.readFile)

const modifiesStateAndIsPublic = declaration =>
!declaration.match(/\b(internal|private|view|pure|constant)\b/)

const typeOrAddress = type => {
const types = ['address', 'byte', 'uint', 'int', 'bool', 'string']

// check if the type starts with any of the above types, otherwise it is probably
// a typed contract, so we need to return address for the signature
return types.filter(t => type.indexOf(t) === 0).length > 0 ? type : 'address'
}

// extracts function signature from function declaration
const getSignature = declaration => {
let [name, params] = declaration.match(/function ([^]*?)\)/)[1].split('(')

if (!name) {
return 'fallback'
}

let argumentNames = []

if (params) {
// Has parameters
const inputs = params
.replace(/\n/gm, '')
.replace(/\t/gm, '')
.split(',')

params = inputs
.map(param => param.split(' ').filter(s => s.length > 0)[0])
.map(type => typeOrAddress(type))
.join(',')

argumentNames = inputs.map(param => param.split(' ').filter(s => s.length > 0)[1] || '')
}

return { sig: `${name}(${params})`, argumentNames }
}

const getNotice = declaration => {
// capture from @notice to either next '* @' or end of comment '*/'
const notices = declaration.match(/(@notice)([^]*?)(\* @|\*\/)/m)
if (!notices || notices.length === 0) return null

return notices[0]
.replace('*/', '')
.replace('* @', '')
.replace('@notice ', '')
.replace(/\n/gm, '')
.replace(/\t/gm, '')
.split(' ')
.filter(x => x.length > 0)
.join(' ')
}

// extracts required role from function declaration
const getRoles = declaration => {
const auths = declaration.match(/auth.?\(([^]*?)\)/gm)
if (!auths) return []

return auths.map(
authStatement =>
authStatement
.split('(')[1]
.split(',')[0]
.split(')')[0]
)
}

// Takes the path to a solidity file and extracts public function signatures,
// its auth role if any and its notice statement
module.exports = async sourceCodePath => {
const sourceCode = await readFile(sourceCodePath, 'utf8')

// everything between every 'function' and '{' and its @notice
const funcDecs = sourceCode.match(/(@notice|^\s*function)(?:[^]*?){/gm)

if (!funcDecs) return []

return funcDecs
.filter(dec => modifiesStateAndIsPublic(dec))
.map(dec => ({
roles: getRoles(dec),
notice: getNotice(dec),
...getSignature(dec),
}))
}
45 changes: 44 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
* @module radspec
*/
const ABI = require('web3-eth-abi')
const MetadataDAO = require('metadata-dao')

const scanner = require('./scanner')
const parser = require('./parser')
const evaluator = require('./evaluator')
Expand Down Expand Up @@ -103,10 +105,51 @@ function evaluate (source, call, options = {}) {
return evaluateRaw(source, parameters, { ...options, to: call.transaction.to })
}

async function metadataDAOEvaluate (call, options = {}) {
const metadataDAO = new MetadataDAO()

// Get method ID
const { to, data } = call.transaction
const methodId = data.substr(0, 10)

const fn = await metadataDAO.query('radspec', 'sig', methodId)

if (!fn) {
return null
}

// If the function was found in local radspec registry. Decode and evaluate.
const { notice: source, signature: sig } = fn

// get the array of input types from the function signature
const inputString = sig.replace(')', '').split('(')[1]

let parameters = []

// If the function has parameters
if (inputString !== '') {
const inputs = inputString.split(',')

// Decode parameters
const parameterValues = ABI.decodeParameters(inputs, '0x' + data.substr(10))
parameters = inputs.reduce((acc, input, i) => (
{
[`$${i + 1}`]: {
type: input,
value: parameterValues[i]
},
...acc
}), {})
}

return await evaluateRaw(source, parameters, { ...options, to })
}

module.exports = {
scan: scanner.scan,
parse: parser.parse,

evaluateRaw,
evaluate
evaluate,
metadataDAOEvaluate
}

0 comments on commit 11de4b1

Please sign in to comment.