Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement evaluation with fetching source from metadata-dao #58

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 { evaluateWithRegistry } = 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 evaluateWithRegistry({ 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),
}))
}
54 changes: 53 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* @module radspec
*/
import ABI from 'web3-eth-abi'
import MetadataDAO from 'metadata-dao'
import { defaultHelpers } from './helpers'
import { evaluateRaw } from './lib'

Expand Down Expand Up @@ -93,9 +94,60 @@ function evaluate (source, call, { userHelpers = {}, ...options } = {}) {
)
}

async function evaluateWithRegistry (call, { userHelpers = {}, ...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
}), {})
}

const availableHelpers = { ...defaultHelpers, ...userHelpers }

return await evaluateRaw(
source,
parameters,
{
...options,
availableHelpers,
to: call.transaction.to
}
)
}

export default evaluate
export { evaluate, evaluateRaw }
export { evaluate, evaluateRaw, evaluateWithRegistry }

// Re-export some commonly used inner functionality
export { parse } from './parser'
export { scan } from './scanner'