29

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?

  • 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
34
+50

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.
}
  • 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
11

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);
    })
}
  • 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 Kmak 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 5 at 20:54
3

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'
}
  • copy paste hmmm – M H Nov 24 '18 at 4:08
  • 1
    @MH you must be new here at SE? The idea is to provide an answer, not just a link. Copy paste is a perfect way to do that. – pors Nov 25 '18 at 8:02
2

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'
  }
}
1

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.

0

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' } } ]

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.