25

After reading the this post, it seems like I should be able to access the logs of events that have been called on an Ethereum contract. The section I am interested in is Option 3 which is using event logs as a cheaper form of storage.

This Stack Exchange question highlights a great way to do this.

The important part:

var filter = web3.eth.filter({
    'fromBlock': 0,
    'toBlock': 'latest',
    'address': contractAddress,
    'topics':[
        web3.sha3('newtest(string,uint256,string,string,uint256)')
    ]
});

filter.watch(function(error, result) {
   ...
})

However, the data I am getting back is not an array of logs in the structure of my event which I was expecting.

Blow is a basic contract where I am taking a product and moving it between addresses. When I do so, I create an event to highlight who sent it and who took ownership as well as the quantity. This should give me a rich history of every address that owned the product and the quantities at each transaction.

Contract:

contract Product{
  address public owner;
  string public title;
  mapping (address => uint) quantity;

  event Transferred(address from, address to, uint quantity);

  // Constructor
  function Product(string _title, uint _quantity){
    owner = msg.sender;
    title = _title;
    quantity[msg.sender] = _quantity;
  }

  function getQuantity(address _user) constant returns (uint _quantity){
    return quantity[_user];
  }

  function changeQuantity(uint _quantity) returns (bool success){
    quantity[msg.sender] = _quantity;
    return true;
  }

  function transfer(address _to, uint _quantity) returns (bool success){
    if (quantity[msg.sender] < _quantity){
      return false;
    }

    owner = _to;
    quantity[msg.sender] -= _quantity;
    quantity[_to] += _quantity;
    Transferred(msg.sender, _to, _quantity);
    return true;
  }
}

Let's start interacting with this bad boy in the Truffle console. First let's make our account objects.

var account1 = web3.eth.accounts[0]
var account2 = web3.eth.accounts[1]

Now, lets access the deployed contract.

var product;
Product.deployed().then(function(i){ product = i });

Now lets make sure account1 has 0 of whatever product this is

product.getQuantity(account1).then(function(ci){console.log(ci)})
// >>> { [String: '0'] s: 1, e: 0, c: [ 0 ] }

Sweet. Lets give it 10 as a quantity.

product.changeQuantity(10).then(function(ci){console.log(ci)})
product.getQuantity(account1).then(function(ci){console.log(ci)})

// >>> { [String: '10'] s: 1, e: 1, c: [ 10 ] }

Now we are cooking! Lets give one widget to account2. This will trigger the Transferred event and hopefully create a log that I can see forever and ever.

product.transfer(account2, 1).then(function(ci){console.log(ci)})
product.getQuantity(account2).then(function(ci){console.log(ci)})
// >>> { [String: '1'] s: 1, e: 0, c: [ 1 ] }

Awesome! Now lets check those logs! Replace the_contract_address with the contract address String.

var filter = web3.eth.filter({
    fromBlock:0,
    toBlock: 'latest',
    address: ’the_contract_address',
    'topics':[
        web3.sha3('Transferred(address,address,uint)')
    ]
});

Printing the filter object will give you:

Filter {
  requestManager:
   RequestManager {
     provider: HttpProvider { host: 'http://localhost:8545', timeout: 0 },
     polls: {},
     timeout: null },
  options:
   { topics: [ '0x6f8b0853f4c56c6a9faec2151763bdc803a695c1ce09b45d3036186c5ae14c47' ],
     from: undefined,
     to: undefined,
     address: '0x570a704dded5379eb21b064cc7750741348e8860',
     fromBlock: '0x0',
     toBlock: 'latest' },
  implementation:
   { newFilter: { [Function: send] request: [Function: bound ], call: [Function: newFilterCall] },
     uninstallFilter: { [Function: send] request: [Function: bound ], call: 'eth_uninstallFilter' },
     getLogs: { [Function: send] request: [Function: bound ], call: 'eth_getFilterLogs' },
     poll: { [Function: send] request: [Function: bound ], call: 'eth_getFilterChanges' } },
  filterId: '0x0b',
  callbacks: [],
  getLogsCallbacks: [],
  pollFilters: [],
  formatter: [Function: outputLogFormatter]
}

From here, I have no idea how to get the log data I am looking for. Even calling the temptingly named function eth_getFilterLogs returns []. I need a way to see a beautiful list of logs that grows over time with each transaction.

Any help would be greatly appreciated!

Edit:

var myEvent = product.Transferred({fromBlock: 0, toBlock: 'latest'});
myEvent.watch(function(error, result){console.log(result)});

The above successfully gives me the last event log it seems. However, it only seems to return one. For instance, executing:

product.transfer(account2, 3).then(function(ci){console.log(ci)})
product.transfer(account2, 2).then(function(ci){console.log(ci)})

yields only the second transaction results.

{ logIndex: 0,
  transactionIndex: 0,
  transactionHash: '0xe2e44b73d67147fb9988dce58d7616da759660a7fde99b3068c6b92fdb9ad8d3',
  blockHash: '0x3a28c6909d44f380a2c64601ecbc3523551ed3840f3afd2549d72d0b710fbb2d',
  blockNumber: 38,
  address: '0x97c553ef6d28b88e19f47735c35369d71f891d76',
  type: 'mined',
  event: 'Transferred',
  args:
   { from: '0x00bd67f06685ad070579b95c2e19ec0022fc916e',
     to: '0x90144809cf261f271e9b7fd3d6d1d0b51d8426c2',
     quantity: {
       [String: '2'] s: 1, e: 0, c: [Object]
     }
   }
}

How can I get both (all) of them?

  • Did you try this - github.com/ethereum/wiki/wiki/JavaScript-API#contract-events. I use events like this - let rateEvent = constructor.Rate({}, {fromBlock: 0, toBlock: 'latest'}); rateEvent.watch(function(err, result) { rateEvent.stopWatching(); ... – Sanchit May 18 '17 at 19:21
  • I responded inline so I could give you a more detailed response. That gives me the last transaction, but not all of them. Any idea how to get an array of all of the transactions? – user1911077 May 23 '17 at 1:25
  • Great solution @Travis Jacobs. Though this solution prints the correct details as per the event that has been fired, still I am having some doubt in the concept. When I see the transaction object (for a transaction which fired the event), there is a mismatch between the 'to' field. The event logs return the correct addresses of the accounts between which the transaction was triggered where as there is some different account address in the transaction object's 'to' field. This is the data from the transaction object. ![enter image description here](https:/ – Anchit Seth Aug 31 '17 at 6:15
19

To get event logs of the past, you can instantiate the event with a block range, and use the myEvent.get function to retrieve events.

In your example, we could do something minimal like this:

let transferEvent = product.Transferred({}, {fromBlock: 0, toBlock: 'latest'})
transferEvent.get((error, logs) => {
  // we have the logs, now print them
  logs.forEach(log => console.log(log.args))
})

Note: The first argument {} can be used like a filter, to only catch certain events.

Above code logs this to console:

{ from: '0xc7d748654199d3594239a244d806e51331ca14b5',                    
  to: '0x3c11b23b4d5adb2d534d262cf83cee773c9b1c0a',                                            
  quantity: { [String: '1'] s: 1, e: 0, c: [ 1 ] } }                                           
{ from: '0xc7d748654199d3594239a244d806e51331ca14b5',                                          
  to: '0x3c11b23b4d5adb2d534d262cf83cee773c9b1c0a',                                            
  quantity: { [String: '3'] s: 1, e: 0, c: [ 3 ] } }                                           

What about for all events of a contract?

Here the event instantiation would just look like this (notice we only use one parameter here):

let events = product.allEvents({fromBlock: 0, toBlock: 'latest'})

And then continue as before, with events.get().

event.get() vs event.watch()

event.watch() is used for dealing with new events that fit a filter, as they stream in ("watching" the blockchain).

event.get() is for grabbing existing events that fit some filter.

Sources:

  • I think this might just be the correct answer! However, before I am able to get the event log in the format you've mentioned, I am getting the filter argument(s) such as: Filter { requestManager: RequestManager { provider: Provider { provider: [Object] }, polls: {}, timeout: null }, options: { topics: [ '0x4d9f84ad939cdfc919f96521fa43928b9d771bdc45c91807a90698918f4c8b2a' ], from: undefined, to: undefined, Idea why might it be so? – Manganese Aug 20 '17 at 22:47
4

For anyone using Web3 ^1.0.0, viewing contract events has changed slightly.

To view contract events from the past, we now use (from https://web3js.readthedocs.io/en/1.0/web3-eth-contract.html#getpastevents):

myContract.getPastEvents('MyEvent', {
    filter: {myIndexedParam: [20,23], myOtherIndexedParam:'0x123456789...'}, // Using an array means OR: e.g. 20 or 23
    fromBlock: 0,
    toBlock: 'latest'
}, function(error, events){ console.log(events); })
.then(function(events){
    console.log(events) // same results as the optional callback above
});

To subscribe to future events, we now use the following syntax:

myContract.events.MyEvent({
    filter: {myIndexedParam: [20,23], myOtherIndexedParam:'0x123456789...'}, // Using an array means OR: e.g. 20 or 23
    fromBlock: 0
}, function(error, event){ console.log(event); })
.on('data', function(event){
    console.log(event); // same results as the optional callback above
})
.on('changed', function(event){
    // remove event from local database
})
.on('error', console.error);
  • it requires websocket connection – rstormsf May 17 '18 at 20:56
  • Subscribing to future events does, that is correct. WS are not currently supported by MetaMask, but Infura provides WS nodes for all testnets and mainnet. – Zack McGinnis May 17 '18 at 23:08
  • I should also add that based on my experience using Infura to subscribe to future events on Rinkeby, it is by no means perfect and disconnects happen fairly often. Something to be aware of. – Zack McGinnis May 17 '18 at 23:11
  • Not true, Infura doesn't provide WS for kovan chain. Correct WS is very unreliable constant disconnects – rstormsf May 18 '18 at 22:07
0

Alternatively, you can wrap your contract into truffle-contract abstraction - if you call contract functions form js that way, you get back a tx object that has the logs included. Take a look how I implemented this: https://github.com/aleybovich/smart-contract-executor/blob/master/src/js/components/FuncForm.js

Or simply put:

Add the module:

const truffleContract = require("truffle-contract");

Create the contract abstraction instance:

const contract = truffleContract({abi});
contract.setProvider(web3.currentProvider);
contract.defaults({ from: fromAddress});

Send the transaction:

contract.at(this.props.contractAddress)
    .then(instance => {
        return instance[funcName](...args);
    })
    .then(tx => {
        if (tx.logs.length) {
            ... Process the logs
        }
    })
    .catch(error => {
        console.error(error);
    });

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.