Skip to content

Commit

Permalink
wrapper: fix callsscripts decoding (#283)
Browse files Browse the repository at this point in the history
  • Loading branch information
sohkai authored Apr 17, 2019
1 parent 60e0a61 commit 0732c12
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 49 deletions.
13 changes: 13 additions & 0 deletions packages/aragon-wrapper/src/evmscript/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ test('encodeCallScript', (t) => {
}, {
to: '0xbeefbeef03c7e5a1c29e0aa675f8e16aee0a5fad',
data: '0xbeef'
}, {
to: '0xbaaabaaa03c7e5a1c29e0aa675f8e16aee0a5fad',
data: '0x'
}])

t.is(
Expand Down Expand Up @@ -45,4 +48,14 @@ test('encodeCallScript', (t) => {
'beef',
'sixth part of callscript should be data for tx 2'
)
t.is(
callScript.slice(114, 154),
'baaabaaa03c7e5a1c29e0aa675f8e16aee0a5fad',
'seventh part of callscript should be address for tx 3'
)
t.is(
callScript.slice(154, 162),
'00000000',
'eigth part of callscript should be data length for tx 3'
)
})
96 changes: 47 additions & 49 deletions packages/aragon-wrapper/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1212,53 +1212,6 @@ export default class Aragon {
).toPromise()
}

/**
* Decodes an EVM callscript and returns the transaction path it describes.
*
* @param {string} script
* @return {Array<Object>} An array of Ethereum transactions that describe each step in the path
*/
decodeTransactionPath (script) {
// TODO: Support callscripts with multiple transactions in one (i.e. one ID, multiple destinations)
function decodePathSegment (script) {
// Remove script identifier
script = script.substr(10)

// Get address
const destination = `0x${script.substr(0, 40)}`
script = script.substr(40)

// Get data
const dataLength = parseInt(`0x${script.substr(0, 8)}`) * 2
script = script.substr(8)
const data = `0x${script.substr(0, dataLength)}`
script = script.substr(dataLength)

return {
to: destination,
data
}
}

let scriptId = script.substr(0, 10)
if (scriptId !== CALLSCRIPT_ID) {
throw new Error(`Unknown script ID ${scriptId}`)
}

let path = []
while (script.startsWith(CALLSCRIPT_ID)) {
const segment = decodePathSegment(script)

// Set script
script = segment.data

// Push segment
path.push(segment)
}

return path
}

/**
* Calculate the transaction path for a transaction to `destination`
* that invokes `methodName` with `params`.
Expand Down Expand Up @@ -1352,6 +1305,51 @@ export default class Aragon {
}
}

/**
* Decodes an EVM callscript and returns the transaction path it describes.
*
* @param {string} script
* @return {Array<Object>} An array of Ethereum transactions that describe each step in the path
*/
decodeTransactionPath (script) {
function decodePathSegment (script) {
// Get address
const to = `0x${script.substring(0, 40)}`
script = script.substring(40)

// Get data
const dataLength = parseInt(`0x${script.substring(0, 8)}`, 16) * 2
script = script.substring(8)
const data = `0x${script.substring(0, dataLength)}`

// Return rest of script for processing
script = script.substring(dataLength)

return {
segment: {
data,
to
},
scriptLeft: script
}
}

// Get script identifier (0x prefix + bytes4)
const scriptId = script.substring(0, 10)
if (scriptId !== CALLSCRIPT_ID) {
throw new Error(`Unknown script ID ${scriptId}`)
}

const segments = []
let scriptData = script.substring(10)
while (scriptData.length > 0) {
const { segment, scriptLeft } = decodePathSegment(scriptData)
segments.push(segment)
scriptData = scriptLeft
}
return segments
}

/**
* Use radspec to create a human-readable description for each transaction in the given `path`
*
Expand All @@ -1369,9 +1367,9 @@ export default class Aragon {
if (!app.functions) return step

// Find the method
const methodId = step.data.substr(2, 8)
const methodId = step.data.substring(2, 10)
const method = app.functions.find(
(method) => keccak256(method.sig).substr(0, 8) === methodId
(method) => keccak256(method.sig).substring(0, 8) === methodId
)

// Method does not exist in artifact
Expand Down
55 changes: 55 additions & 0 deletions packages/aragon-wrapper/src/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import proxyquire from 'proxyquire'
import { Subject, empty, of, from } from 'rxjs'
import { first } from 'rxjs/operators'
import { getKernelNamespace } from './core/aragonOS'
import { encodeCallScript } from './evmscript'
import AsyncRequestCache from './utils/AsyncRequestCache'

// soliditySha3('app')
Expand Down Expand Up @@ -1300,3 +1301,57 @@ test('should throw if no ABI is found, when calculating the transaction path', a
*/
})
})

test('should be able to decode an evm call script with a single transaction', async (t) => {
const { Aragon } = t.context

t.plan(1)
// arrange
const instance = new Aragon()
const script = encodeCallScript([{
to: '0xcafe1a77e84698c83ca8931f54a755176ef75f2c',
data: '0xcafe'
}])
// act
const decodedScript = instance.decodeTransactionPath(script)
// assert
t.deepEqual(decodedScript, [
{
data: '0xcafe',
to: '0xcafe1a77e84698c83ca8931f54a755176ef75f2c'
}
])
})

test('should be able to decode an evm call script with multiple transactions', async (t) => {
const { Aragon } = t.context

t.plan(1)
// arrange
const instance = new Aragon()
const script = encodeCallScript([{
to: '0xcafe1a77e84698c83ca8931f54a755176ef75f2c',
data: '0xcafe'
}, {
to: '0xbeefbeef03c7e5a1c29e0aa675f8e16aee0a5fad',
data: '0xbeef'
}, {
to: '0xbaaabaaa03c7e5a1c29e0aa675f8e16aee0a5fad',
data: '0x'
}])
// act
const decodedScript = instance.decodeTransactionPath(script)
// assert
t.deepEqual(decodedScript, [
{
to: '0xcafe1a77e84698c83ca8931f54a755176ef75f2c',
data: '0xcafe'
}, {
to: '0xbeefbeef03c7e5a1c29e0aa675f8e16aee0a5fad',
data: '0xbeef'
}, {
to: '0xbaaabaaa03c7e5a1c29e0aa675f8e16aee0a5fad',
data: '0x'
}
])
})

0 comments on commit 0732c12

Please sign in to comment.