|
|
|
'use strict'
|
|
|
|
import { ethers } from 'ethers'
|
|
|
|
import { visitContracts } from './txHelper'
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Register to txListener and extract events
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
export class EventsDecoder {
|
|
|
|
resolveReceipt
|
|
|
|
|
|
|
|
constructor ({resolveReceipt}) {
|
|
|
|
this.resolveReceipt = resolveReceipt
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* use Transaction Receipt to decode logs. assume that the transaction as already been resolved by txListener.
|
|
|
|
* logs are decoded only if the contract if known by remix.
|
|
|
|
*
|
|
|
|
* @param {Object} tx - transaction object
|
|
|
|
* @param {Function} cb - callback
|
|
|
|
*/
|
|
|
|
parseLogs (tx, contractName, compiledContracts, cb) {
|
|
|
|
if (tx.isCall) return cb(null, { decoded: [], raw: [] })
|
|
|
|
this.resolveReceipt(tx, (error, receipt) => {
|
|
|
|
if (error) return cb(error)
|
|
|
|
this._decodeLogs(tx, receipt, contractName, compiledContracts, cb)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
_decodeLogs (tx, receipt, contract, contracts, cb) {
|
|
|
|
if (!contract || !receipt) {
|
|
|
|
return cb('cannot decode logs - contract or receipt not resolved ')
|
|
|
|
}
|
|
|
|
if (!receipt.logs) {
|
|
|
|
return cb(null, { decoded: [], raw: [] })
|
|
|
|
}
|
|
|
|
this._decodeEvents(tx, receipt.logs, contract, contracts, cb)
|
|
|
|
}
|
|
|
|
|
|
|
|
_eventABI (contract) {
|
|
|
|
const eventABI = {}
|
|
|
|
const abi = new ethers.utils.Interface(contract.abi)
|
|
|
|
for (let e in abi.events) {
|
|
|
|
const event = abi.getEvent(e)
|
|
|
|
eventABI[abi.getEventTopic(e).replace('0x', '')] = { event: event.name, inputs: event.inputs, object: event, abi: abi }
|
|
|
|
}
|
|
|
|
return eventABI
|
|
|
|
}
|
|
|
|
|
|
|
|
_eventsABI (compiledContracts) {
|
|
|
|
const eventsABI = {}
|
|
|
|
visitContracts(compiledContracts, (contract) => {
|
|
|
|
eventsABI[contract.name] = this._eventABI(contract.object)
|
|
|
|
})
|
|
|
|
return eventsABI
|
|
|
|
}
|
|
|
|
|
|
|
|
_event (hash, eventsABI) {
|
|
|
|
for (let k in eventsABI) {
|
|
|
|
if (eventsABI[k][hash]) {
|
|
|
|
let event = eventsABI[k][hash]
|
|
|
|
for (let input of event.inputs) {
|
|
|
|
if (input.type === 'function') {
|
|
|
|
input.type = 'bytes24'
|
|
|
|
input.baseType = 'bytes24'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return event
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
|
|
|
_stringifyBigNumber (value) {
|
|
|
|
return value._isBigNumber ? value.toString() : value
|
|
|
|
}
|
|
|
|
|
|
|
|
_stringifyEvent (value) {
|
|
|
|
if (value === null || value === undefined) return ' - '
|
|
|
|
if (value._ethersType) value.type = value._ethersType
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
// for struct && array
|
|
|
|
return value.map((item) => { return this._stringifyEvent(item) })
|
|
|
|
} else {
|
|
|
|
return this._stringifyBigNumber(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_decodeEvents (tx, logs, contractName, compiledContracts, cb) {
|
|
|
|
const eventsABI = this._eventsABI(compiledContracts)
|
|
|
|
const events = []
|
|
|
|
for (let i in logs) {
|
|
|
|
// [address, topics, mem]
|
|
|
|
const log = logs[i]
|
|
|
|
const topicId = log.topics[0]
|
|
|
|
const eventAbi = this._event(topicId.replace('0x', ''), eventsABI)
|
|
|
|
if (eventAbi) {
|
|
|
|
const decodedlog = eventAbi.abi.parseLog(log)
|
|
|
|
const decoded = {}
|
|
|
|
for (const v in decodedlog.args) {
|
|
|
|
decoded[v] = this._stringifyEvent(decodedlog.args[v])
|
|
|
|
}
|
|
|
|
events.push({ from: log.address, topic: topicId, event: eventAbi.event, args: decoded })
|
|
|
|
} else {
|
|
|
|
events.push({ from: log.address, data: log.data, topics: log.topics })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cb(null, { decoded: events, raw: logs })
|
|
|
|
}
|
|
|
|
}
|