Skip to content

Commit

Permalink
feat(ACI): Implement Contract event transformation based on ACI schema
Browse files Browse the repository at this point in the history
@todo: investigate and handle all types allowed in event body
  • Loading branch information
nduchak committed Feb 4, 2020
1 parent de4787e commit ff25b10
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 7 deletions.
3 changes: 2 additions & 1 deletion es/contract/aci/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export function getFunctionACI (aci, name) {
state: aci.state,
typedef: aci.type_defs,
contractName: aci.name
}
},
event: aci.event.variant
}
}

Expand Down
23 changes: 17 additions & 6 deletions es/contract/aci/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@

import * as R from 'ramda'

import { validateArguments, transform, transformDecodedData } from './transformation'
import {
validateArguments,
transform,
transformDecodedData,
transformDecodedEvents
} from './transformation'
import { buildContractMethods, getFunctionACI } from './helpers'
import { isAddressValid } from '../../utils/crypto'
import AsyncInit from '../../utils/async-init'
Expand Down Expand Up @@ -160,6 +165,16 @@ async function getContractInstance (source, { aci, contractAddress, filesystem =
return instance
}

const decodeCallResult = async (result, fnACI, opt) => {
return {
decodedResult: await transformDecodedData(
fnACI.returns,
await result.decode(),
{ ...opt, bindings: fnACI.bindings }
),
decodedEvents: transformDecodedEvents(result.result.log, fnACI, opt)
}
}
const call = ({ client, instance }) => async (fn, params = [], options = {}) => {
const opt = R.merge(instance.options, options)
const fnACI = getFunctionACI(instance.aci, fn)
Expand All @@ -180,11 +195,7 @@ const call = ({ client, instance }) => async (fn, params = [], options = {}) =>
: await client.contractCall(source, instance.deployInfo.address, fn, params, opt)
return {
...result,
decodedResult: opt.waitMined ? await transformDecodedData(
fnACI.returns,
await result.decode(),
{ ...opt, bindings: fnACI.bindings }
) : null
...opt.waitMined ? await decodeCallResult(result, fnACI, opt) : {}
}
}

Expand Down
55 changes: 55 additions & 0 deletions es/contract/aci/transformation.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import Joi from 'joi-browser'
import { toBytes } from '../../utils/bytes'
import { addressFromDecimal, hash } from '../../utils/crypto'
import { decode } from '../../tx/builder/helpers'

export const SOPHIA_TYPES = [
'int',
Expand Down Expand Up @@ -168,6 +171,58 @@ export function transformMap (value, generic, { bindings }) {

// FUNCTION RETURN VALUE TRANSFORMATION ↓↓↓

/**
* Transform decoded event to JS type
* @param events
* @param fnACI
* @return {*}
*/
export function transformDecodedEvents (events, fnACI) {
if (!events.length) return []

const eventsSchema = fnACI.event.map(e => {
const name = Object.keys(e)[0]
return { name, value: e[name], nameHash: hash(name).toString('hex') }
})

return events.map(l => {
const [eName, ...eParams] = l.topics
const hexHash = toBytes(eName, true).toString('hex')
const { schema, isHasNonIndexed } = eventsSchema
.reduce(
(acc, el) => {
if (el.nameHash === hexHash) {
return { schema: el.value.filter(e => e !== 'string'), isHasNonIndexed: el.value.includes('string') }
}
return acc
},
{ schema: [], isHasNonIndexed: false }
)
return {
...l,
decoded: [
...isHasNonIndexed ? [decode(l.data).toString('utf-8')] : [],
...eParams.map((event, i) => transformEvent(event, schema[i]))
]
}
})
}

function transformEvent (event, type) {
switch (type) {
case SOPHIA_TYPES.bool:
return !!event
case SOPHIA_TYPES.string:
return toBytes(event, true).toString()
case SOPHIA_TYPES.hash:
return toBytes(event, true).toString('hex')
case SOPHIA_TYPES.address:
return addressFromDecimal(event)
default:
return event
}
}

/**
* Transform decoded data to JS type
* @param aci
Expand Down
10 changes: 10 additions & 0 deletions test/integration/contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ contract StateContract =
record state = { value: string, key: number, testOption: option(string) }
record yesEr = { t: number}
datatype event = TheFirstEvent(int) | AnotherEvent(string, address) | AnotherEvent2(string, bool, int)
datatype dateUnit = Year | Month | Day
datatype one_or_both('a, 'b) = Left('a) | Right('b) | Both('a, 'b)
Expand Down Expand Up @@ -104,6 +105,10 @@ contract StateContract =
Left(x) => x
Right(_) => abort("asdasd")
Both(x, _) => x
entrypoint emitEvents() : unit =
Chain.event(TheFirstEvent(42))
Chain.event(AnotherEvent("This is not indexed", Contract.address))
Chain.event(AnotherEvent2("This is not indexed", true, 1))
`

const encodedNumberSix = 'cb_DA6sWJo='
Expand All @@ -129,6 +134,11 @@ describe('Contract', function () {
const code = await contract.contractCompile(identityContract)
return contract.contractDeploy(code.bytecode, identityContract).should.eventually.have.property('address')
})
it.skip('Events', async () => {
const cInstance = await contract.getContractInstance(testContract, { filesystem })
await cInstance.deploy(['test', 1, 'some'])
const eventResult = await cInstance.methods.emitEvents()
})

it('compiles Sophia code', async () => {
bytecode = await contract.contractCompile(identityContract)
Expand Down

0 comments on commit ff25b10

Please sign in to comment.