47

The event parser in web3 provides a nice parsing functionality for events, and I use it for logging all events to a file, but it's very difficult to use for looking at individual events for a particular transaction for automated testing because it induces an unneeded and difficult to manage concurrency aspect when there doesn't need to be one.

When I get a transaction result with web3, I have the transaction receipt in hand and that's the perfect time to synchronously examine the result and apply pass/fail test criteria.

I would like to parse the log section of the receipt but can't find a function in web3 to do so. Does it exist?

q9f
  • 32,913
  • 47
  • 156
  • 395
Paul S
  • 4,271
  • 1
  • 22
  • 48
  • Since the more recent answers haven't been upvoted enough, it should be made clear here that web3 versions 1.0 and later provide an API to do this for you, you no longer need to hack it together yourself. – Paul S Sep 13 '18 at 15:31

6 Answers6

42

Do this: You'll need to pull code from web3, and it works best if your frontend is bundled using something like webpack or browserify:

var SolidityCoder = require("web3/lib/solidity/coder.js");
var log = receipt.logs[0];
var data = SolidityCoder.decodeParams(["string", "uint"], log.data.replace("0x", ""));

In this case, we're decoding log data that contains two variables, one a string type and one a uint.

EDIT:

If you have the ABI available, you can detect which event it is related to that ABI:

var SolidityCoder = require("web3/lib/solidity/coder.js");

// You might want to put the following in a loop to handle all logs in this receipt.
var log = receipt.logs[0];
var event = null;

for (var i = 0; i < abi.length; i++) {
  var item = abi[i];
  if (item.type != "event") continue;
  var signature = item.name + "(" + item.inputs.map(function(input) {return input.type;}).join(",") + ")";
  var hash = web3.sha3(signature);
  if (hash == log.topics[0]) {
    event = item;
    break;
  }
}

if (event != null) {
  var inputs = event.inputs.map(function(input) {return input.type;});
  var data = SolidityCoder.decodeParams(inputs, log.data.replace("0x", ""));
  // Do something with the data. Depends on the log and what you're using the data for.
}
Tim Coulter
  • 1,049
  • 1
  • 7
  • 9
  • Shouldn't this use the compiler generated ABI to decode it? That's how it works with events... – Paul S Feb 23 '16 at 19:21
  • The log data is ABI encoded. The types specified ("string, then "uint"), tell the decoder how to decode it. You don't need the full contract ABI - just the types related to the specific log. Let me edit my answer to better describe how this works. – Tim Coulter Feb 23 '16 at 19:24
  • Answer edited. Should give you more of an idea how to handle the log data. – Tim Coulter Feb 23 '16 at 19:36
  • The problem is I don't have to specify the types for an event, a JSON object is automagically created. Creating manually created JSON types from examining solidity code is tedious and error prone. Perhaps I should change the question to "automatically parse", since the procedure you are proposing is manual. – Paul S Feb 23 '16 at 20:52
  • I'm not sure what you mean between "automatic" and "manual" in this context. If you want to turn log data (a hexadecimal string) into Javascript objects, this is how you do it. Web3 does not provide a function to do this for you. If you'd like this function, I recommend you submit this code to Web3 and try to get it included. Cheers! – Tim Coulter Feb 23 '16 at 21:03
  • I'm fine with creating the code, but I'd like some hints about where to start. how does the event handler code figure out the event name, look it up in the ABI, and create the JSON object for the even?

    I can't get to it until late March, I have other deadlines.

    – Paul S Feb 23 '16 at 21:13
  • Hmm, there's some caching issue with stackexchange, I didn't see your code added for some reason. I see now it's completely data driven, not manual. All I need is the ABI and the event log and everything is good. I'll award the bounty. Now, do you want to check this into Web3 or do you want me to? In late March I'm going to be adding a first class transaction object to my extensions of web3, question is does the community want it or not... – Paul S Feb 23 '16 at 21:23
  • Feel free to submit. Not sure regarding your transaction object. But I've been using this contract abstraction for the last couple months (PS: I wrote it) and it works well for me. Would love your thoughts. https://github.com/ConsenSys/ether-pudding – Tim Coulter Feb 24 '16 at 00:09
  • I pinged you on gitter – Paul S Feb 25 '16 at 05:56
  • I am trying the same thing, but cannot find the coder.js, can any one tell me the location on web3/lib on windows OS – SwapnilKumbhar Jun 02 '17 at 06:53
  • @Tim Coulter @Paul S Would you please mention how to change SolidityCoder.decodeParams(["string", "uint"], log.data.replace("0x", "")); parameters when our function input type is an array like this : function newObject(bytes32 _id, uint256 number_of_sub_states, bytes32[10] sub_states_types, bytes32[10] sub_states_values, address _owner) ? Thank you. – Questioner May 08 '18 at 20:32
  • @Tim Coulter @Paul S When i use var SolidityCoder = require("web3/lib/solidity/coder.js"); i receive this error : Error: Cannot find module 'web3/lib/solidity/coder.js' Do you know what is the reason?, Thanks. – Questioner May 10 '18 at 00:21
  • probably newer version of web3 has rearranged internals – Paul S May 12 '18 at 06:00
16

You can now use the web3.eth.abi.decodeLog function (web3 1.0).

Example from the documentation:

web3.eth.abi.decodeLog([{
    type: 'string',
    name: 'myString'
},{
    type: 'uint256',
    name: 'myNumber',
    indexed: true
},{
    type: 'uint8',
    name: 'mySmallNumber',
    indexed: true
}],
'0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000748656c6c6f252100000000000000000000000000000000000000000000000000',
['0x000000000000000000000000000000000000000000000000000000000000f310', '0x0000000000000000000000000000000000000000000000000000000000000010']);
> Result {
    '0': 'Hello%!',
    '1': '62224',
    '2': '16',
    myString: 'Hello%!',
    myNumber: '62224',
    mySmallNumber: '16'
}
pors
  • 443
  • 4
  • 10
15

Tim:

Thanks so much for the pointer. You forced me to finally understand some of the internals of web3.js.

I found a cleaner way to do this that covers all the corner cases of the actually fairly complicated log message format (e.g. indexing). I just used SolidityEvent from web3 to do the already-tested work for me.

Below is the code. I have this code on github, including test code.

// XXX move this to a hook function
var SolidityEvent = require("web3/lib/web3/event.js");
Pudding.logParser = function (logs, abi) {

    // pattern similar to lib/web3/contract.js:  addEventsToContract()
    var decoders = abi.filter(function (json) {
        return json.type === 'event';
    }).map(function(json) {
        // note first and third params required only by enocde and execute;
        // so don't call those!
        return new SolidityEvent(null, json, null);
    });

    return logs.map(function (log) {
        return decoders.find(function(decoder) {
            return (decoder.signature() == log.topics[0].replace("0x",""));
        }).decode(log);
    })
}
Paul S
  • 4,271
  • 1
  • 22
  • 48
  • note if you have multiple contracts you need the ABIs for all the contracts. I'm not going to keep this example up to date. If you want up to date, follow the github link. I've already fixed a few bugs. Cascading array functions is very bad, as an empty array causes an error to be thrown by javascript... – Paul S Apr 10 '16 at 16:31
  • 1
    This is very good answer if someone works with web v0.20.x. – Daniel Dec 29 '18 at 13:07
  • Yes, the comment is hidden above but we recent versions of web3.js the transaction receipt is parsed for you. – Paul S Jan 05 '19 at 20:54
3

For web3.js 1.0 use the following:

contractInstance.inputs = [{"indexed": false, "name": "_id", "type": "uint256"}]; //event abi
contractInstance._decodeEventABI({data: '0x0'}); //event raw data

output

{
  returnValues: 
   Result {
     '0': '1',
     _id: '1',
  },
  raw: {
    data: '0x0'
  }
}
Eugene
  • 219
  • 1
  • 3
2

Once you have transaction receipt (tr), you know block number of the transaction (tr.blockNumber). So, you may do the following:

myContract.MyEvent (
  {},
  {fromBlock: tr.blockNumber, toBlock: tr.blockNumber}).
    get ().
      filter (function (e) {
        return e.transactionHash == tr.transactionHash
      });

This will return an array of all events of type MyEvent generated by contract myContract in transaction referred by transaction receipt tr.

Mikhail Vladimirov
  • 7,313
  • 1
  • 23
  • 38
1

With web3.js 0.20.6.

$ node
> var AllEvents = require('web3/lib/web3/allevents')
undefined
> var decodeEventsForContract = (C, tr) => {
    const ae = new AllEvents(C._web3, C.abi, C.address);

    // ae.decode mutates the args, so we deep copy
    return JSON.parse(JSON.stringify(tr)) 
      .logs
      .filter(l => l.address === C.address)
      .map(l => ae.decode(l));
  }
undefined
> decodeEventsForContract(MyTokenContract, txreceipt);
[ { logIndex: 0,
    transactionIndex: 0,
    transactionHash: '0xbc68d5ddc391fab84cd633a77dbc815cbc42546a13de9d123f7a5b820faa3cb4',
    blockHash: '0xb604998aa0b6bece492530c9cbab494349a31b18a34067f225e1b59613952051',
    blockNumber: 21,
    address: '0x345ca3e014aaf5dca488057592ee47305d9b3e10',
    type: 'mined',
    event: 'Transfer',
    args: 
     { from: '0x0000000000000000000000000000000000000000',
       to: '0x627306090abab3a6e1400e9345bc60c78a8bef57',
       value: [BigNumber] } },
  { logIndex: 1,
    transactionIndex: 0,
    transactionHash: '0xbc68d5ddc391fab84cd633a77dbc815cbc42546a13de9d123f7a5b820faa3cb4',
    blockHash: '0xb604998aa0b6bece492530c9cbab494349a31b18a34067f225e1b59613952051',
    blockNumber: 21,
    address: '0x345ca3e014aaf5dca488057592ee47305d9b3e10',
    type: 'mined',
    event: 'Mint',
    args: 
     { to: '0x627306090abab3a6e1400e9345bc60c78a8bef57',
       value: [BigNumber],
       minter: '0x627306090abab3a6e1400e9345bc60c78a8bef57' } } ]
pkoch
  • 111
  • 3