diff --git a/es/contract/aci/helpers.js b/es/contract/aci/helpers.js index 3a37b6a107..19a657ec8d 100644 --- a/es/contract/aci/helpers.js +++ b/es/contract/aci/helpers.js @@ -17,7 +17,8 @@ export function getFunctionACI (aci, name) { state: aci.state, typedef: aci.type_defs, contractName: aci.name - } + }, + event: aci.event.variant } } diff --git a/es/contract/aci/index.js b/es/contract/aci/index.js index bb029f14ba..716c326561 100644 --- a/es/contract/aci/index.js +++ b/es/contract/aci/index.js @@ -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' @@ -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) @@ -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) : {} } } diff --git a/es/contract/aci/transformation.js b/es/contract/aci/transformation.js index 5f21d23bfc..0aa02bb106 100644 --- a/es/contract/aci/transformation.js +++ b/es/contract/aci/transformation.js @@ -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', @@ -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 diff --git a/test/integration/contract.js b/test/integration/contract.js index 7ee9190024..071929c55b 100644 --- a/test/integration/contract.js +++ b/test/integration/contract.js @@ -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) @@ -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=' @@ -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)