Skip to content
This repository was archived by the owner on Sep 3, 2019. It is now read-only.

Commit 670cdce

Browse files
authored
Merge pull request #39 from bodhiproject/event_hash_dynamic
Event hash dynamic
2 parents bf2cc31 + 4abaf07 commit 670cdce

16 files changed

Lines changed: 467 additions & 151 deletions

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ before_install:
2323

2424
install:
2525
- tar -xvzf qtum-0.14.13-x86_64-linux-gnu.tar.gz
26-
- tar -xvzf ./test/data/qtum_data.tar.gz -C ./test/data
2726
- npm install
27+
- mkdir test/data/.qtum
2828

2929
script:
3030
- cd qtum-0.14.13/bin; ./qtumd -testnet -logevents -rpcuser=bodhi -rpcpassword=bodhi -datadir=/home/travis/build/bodhiproject/qweb3.js/test/data/.qtum &

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,27 @@ async function listUnspent() {
8585

8686
### searchLogs(fromBlock, toBlock, addresses, topics, contractMetadata, removeHexPrefix)
8787
Gets the logs given the params on the blockchain.
88+
89+
The `contractMetadata` param contains the contract names and ABI that you would like to parse. An example of one is:
90+
```
91+
# contract_metadata.js
92+
module.exports = {
93+
EventFactory: {
94+
address: 'd53927df927be7fc51ce8bf8b998cb6611c266b0',
95+
abi: [{ constant: true, inputs: [{ name: '', type: 'bytes32' }], name: 'topics', outputs: [{ name: '', type: 'address' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: false, inputs: [{ name: '_oracle', type: 'address' }, { name: '_name', type: 'bytes32[10]' }, { name: '_resultNames', type: 'bytes32[10]' }, { name: '_bettingEndBlock', type: 'uint256' }, { name: '_resultSettingEndBlock', type: 'uint256' }], name: 'createTopic', outputs: [{ name: 'topicEvent', type: 'address' }], payable: false, stateMutability: 'nonpayable', type: 'function' }, { constant: true, inputs: [{ name: '_name', type: 'bytes32[10]' }, { name: '_resultNames', type: 'bytes32[10]' }, { name: '_bettingEndBlock', type: 'uint256' }, { name: '_resultSettingEndBlock', type: 'uint256' }], name: 'doesTopicExist', outputs: [{ name: '', type: 'bool' }], payable: false, stateMutability: 'view', type: 'function' }, { inputs: [{ name: '_addressManager', type: 'address' }], payable: false, stateMutability: 'nonpayable', type: 'constructor' }, { anonymous: false, inputs: [{ indexed: true, name: '_topicAddress', type: 'address' }, { indexed: true, name: '_creator', type: 'address' }, { indexed: true, name: '_oracle', type: 'address' }, { indexed: false, name: '_name', type: 'bytes32[10]' }, { indexed: false, name: '_resultNames', type: 'bytes32[10]' }, { indexed: false, name: '_bettingEndBlock', type: 'uint256' }, { indexed: false, name: '_resultSettingEndBlock', type: 'uint256' }], name: 'TopicCreated', type: 'event' }],
96+
},
97+
98+
TopicEvent: {
99+
abi: [{ constant: false, inputs: [{ name: '_resultIndex', type: 'uint8' }, { name: '_sender', type: 'address' }, { name: '_amount', type: 'uint256' }], name: 'voteFromOracle', outputs: [{ name: '', type: 'bool' }], payable: false, stateMutability: 'nonpayable', type: 'function' }, { constant: true, inputs: [], name: 'totalBotValue', outputs: [{ name: '', type: 'uint256' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: true, inputs: [{ name: '_oracleIndex', type: 'uint8' }], name: 'getOracle', outputs: [{ name: '', type: 'address' }, { name: '', type: 'bool' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: true, inputs: [{ name: '', type: 'address' }], name: 'didWithdraw', outputs: [{ name: '', type: 'bool' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: true, inputs: [], name: 'resultSet', outputs: [{ name: '', type: 'bool' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: true, inputs: [], name: 'status', outputs: [{ name: '', type: 'uint8' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: true, inputs: [], name: 'getFinalResult', outputs: [{ name: '', type: 'uint8' }, { name: '', type: 'string' }, { name: '', type: 'bool' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: true, inputs: [{ name: '', type: 'uint256' }], name: 'resultNames', outputs: [{ name: '', type: 'bytes32' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: true, inputs: [{ name: '', type: 'uint256' }], name: 'oracles', outputs: [{ name: 'didSetResult', type: 'bool' }, { name: 'oracleAddress', type: 'address' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: false, inputs: [], name: 'finalizeResult', outputs: [{ name: '', type: 'bool' }], payable: false, stateMutability: 'nonpayable', type: 'function' }, { constant: false, inputs: [{ name: '_oracle', type: 'address' }, { name: '_resultIndex', type: 'uint8' }, { name: '_consensusThreshold', type: 'uint256' }], name: 'centralizedOracleSetResult', outputs: [], payable: false, stateMutability: 'nonpayable', type: 'function' }, { constant: true, inputs: [], name: 'totalQtumValue', outputs: [{ name: '', type: 'uint256' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: false, inputs: [{ name: '_consensusThreshold', type: 'uint256' }], name: 'invalidateOracle', outputs: [], payable: false, stateMutability: 'nonpayable', type: 'function' }, { constant: true, inputs: [], name: 'getBetBalances', outputs: [{ name: '', type: 'uint256[10]' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: true, inputs: [], name: 'owner', outputs: [{ name: '', type: 'address' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: true, inputs: [], name: 'calculateQtumContributorWinnings', outputs: [{ name: '', type: 'uint256' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: true, inputs: [], name: 'getVoteBalances', outputs: [{ name: '', type: 'uint256[10]' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: true, inputs: [], name: 'getTotalVotes', outputs: [{ name: '', type: 'uint256[10]' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: false, inputs: [{ name: '_better', type: 'address' }, { name: '_resultIndex', type: 'uint8' }], name: 'bet', outputs: [], payable: true, stateMutability: 'payable', type: 'function' }, { constant: false, inputs: [{ name: '_resultIndex', type: 'uint8' }, { name: '_currentConsensusThreshold', type: 'uint256' }], name: 'votingOracleSetResult', outputs: [{ name: '', type: 'bool' }], payable: false, stateMutability: 'nonpayable', type: 'function' }, { constant: true, inputs: [], name: 'getTotalBets', outputs: [{ name: '', type: 'uint256[10]' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: true, inputs: [], name: 'getEventName', outputs: [{ name: '', type: 'string' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: true, inputs: [], name: 'invalidResultIndex', outputs: [{ name: '', type: 'uint8' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: true, inputs: [], name: 'numOfResults', outputs: [{ name: '', type: 'uint8' }], payable: false, stateMutability: 'view', type: 'function' }, { constant: false, inputs: [], name: 'withdrawWinnings', outputs: [], payable: false, stateMutability: 'nonpayable', type: 'function' }, { constant: false, inputs: [{ name: '_newOwner', type: 'address' }], name: 'transferOwnership', outputs: [], payable: false, stateMutability: 'nonpayable', type: 'function' }, { constant: true, inputs: [], name: 'calculateBotContributorWinnings', outputs: [{ name: '', type: 'uint256' }], payable: false, stateMutability: 'view', type: 'function' }, { inputs: [{ name: '_owner', type: 'address' }, { name: '_centralizedOracle', type: 'address' }, { name: '_name', type: 'bytes32[10]' }, { name: '_resultNames', type: 'bytes32[10]' }, { name: '_bettingEndBlock', type: 'uint256' }, { name: '_resultSettingEndBlock', type: 'uint256' }, { name: '_addressManager', type: 'address' }], payable: false, stateMutability: 'nonpayable', type: 'constructor' }, { payable: true, stateMutability: 'payable', type: 'fallback' }, { anonymous: false, inputs: [{ indexed: true, name: '_eventAddress', type: 'address' }, { indexed: false, name: '_finalResultIndex', type: 'uint8' }], name: 'FinalResultSet', type: 'event' }, { anonymous: false, inputs: [{ indexed: true, name: '_winner', type: 'address' }, { indexed: false, name: '_qtumTokenWon', type: 'uint256' }, { indexed: false, name: '_botTokenWon', type: 'uint256' }], name: 'WinningsWithdrawn', type: 'event' }, { anonymous: false, inputs: [{ indexed: true, name: '_previousOwner', type: 'address' }, { indexed: true, name: '_newOwner', type: 'address' }], name: 'OwnershipTransferred', type: 'event' }],
100+
},
101+
};
102+
88103
```
104+
105+
Usage:
106+
```
107+
import ContractMetadata from './data/contract_metadata';
108+
89109
async function(args) {
90110
let {
91111
fromBlock, // number
@@ -101,7 +121,8 @@ async function(args) {
101121
topics = [];
102122
}
103123
104-
return await qClient.searchLogs(fromBlock, toBlock, addresses, topics, ContractMetadata, true);
124+
// removeHexPrefix = true removes the '0x' hex prefix from all hex values
125+
return await qClient.searchLogs(fromBlock, toBlock, addresses, topics, contractMetadata, true);
105126
}
106127
```
107128

dist/contract.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ var Contract = function () {
124124
}
125125

126126
var dataHex = '';
127-
dataHex = dataHex.concat(_encoder2.default.getFunctionHash(methodObj));
127+
dataHex = dataHex.concat(_encoder2.default.objToHash(methodObj, true));
128128

129129
var hex = void 0;
130130
_lodash2.default.each(methodObj.inputs, function (item, index) {

dist/encoder.js

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,31 +34,38 @@ var Encoder = function () {
3434
}
3535

3636
_createClass(Encoder, null, [{
37-
key: 'getFunctionHash',
37+
key: 'objToHash',
3838

3939
/*
40-
* Converts an object of a method from the ABI to a function hash.
41-
* @param methodObj The json object of the method taken from the ABI.
42-
* @return The function hash.
40+
* Converts an ABI object signature to its hash format.
41+
* @param obj The object of the ABI object.
42+
* @param isFunction Is converting a function object.
43+
* @return The object hash.
4344
*/
44-
value: function getFunctionHash(methodObj) {
45-
if (!methodObj) {
46-
throw new Error('methodObj should not be undefined');
45+
value: function objToHash(obj, isFunction) {
46+
if (_lodash2.default.isUndefined(obj)) {
47+
throw new Error('obj should not be undefined');
48+
}
49+
if (_lodash2.default.isUndefined(isFunction)) {
50+
throw new Error('isFunction should not be undefined');
4751
}
4852

49-
var name = methodObj.name;
53+
var name = obj.name;
5054
var params = '';
51-
for (var i = 0; i < methodObj.inputs.length; i++) {
52-
params = params.concat(methodObj.inputs[i].type);
55+
for (var i = 0; i < obj.inputs.length; i++) {
56+
params = params.concat(obj.inputs[i].type);
5357

54-
if (i < methodObj.inputs.length - 1) {
58+
if (i < obj.inputs.length - 1) {
5559
params = params.concat(',');
5660
}
5761
}
58-
var signature = name.concat('(').concat(params).concat(')');
62+
var hash = name.concat('(' + params + ')');
5963

60-
// Return only the first 4 bytes
61-
return _web3Utils2.default.sha3(signature).slice(2, 10);
64+
if (isFunction) {
65+
// Return only the first 4 bytes
66+
return _web3Utils2.default.sha3(hash).slice(2, 10);
67+
}
68+
return _web3Utils2.default.sha3(hash).slice(2);
6269
}
6370

6471
/*

dist/formatter.js

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ var _utils = require('./utils');
1818

1919
var _utils2 = _interopRequireDefault(_utils);
2020

21+
var _encoder = require('./encoder');
22+
23+
var _encoder2 = _interopRequireDefault(_encoder);
24+
2125
var _decoder = require('./decoder');
2226

2327
var _decoder2 = _interopRequireDefault(_decoder);
@@ -42,33 +46,41 @@ var Formatter = function () {
4246
* @return {object} Decoded searchlog output
4347
*/
4448
value: function searchLogOutput(rawOutput, contractMetadata, removeHexPrefix) {
49+
// Create dict of all event hashes
50+
var eventHashes = {};
51+
_lodash2.default.each(contractMetadata, function (contractItem, contractKey) {
52+
var filteredEvents = _lodash2.default.filter(contractItem.abi, { type: 'event' });
53+
54+
_lodash2.default.each(filteredEvents, function (eventObj) {
55+
var hash = _encoder2.default.objToHash(eventObj, false);
56+
eventHashes[hash] = {
57+
contract: contractKey,
58+
event: eventObj.name
59+
};
60+
});
61+
});
62+
4563
return _lodash2.default.map(rawOutput, function (resultEntry) {
4664
var formatted = _lodash2.default.assign({}, resultEntry);
4765

4866
if (!_lodash2.default.isEmpty(resultEntry.log)) {
4967
_lodash2.default.each(resultEntry.log, function (item, index) {
50-
var eventHash = item.topics[0];
68+
var eventHashObj = eventHashes[item.topics[0]];
5169

52-
var eventName = void 0;
53-
var metadataObj = void 0;
54-
_lodash2.default.each(contractMetadata, function (contractItem, index) {
55-
eventName = _lodash2.default.invert(contractItem)[eventHash];
56-
57-
if (eventName) {
58-
metadataObj = contractItem;
59-
return false;
60-
}
61-
});
70+
var contractObj = void 0;
71+
if (eventHashObj) {
72+
contractObj = contractMetadata[eventHashObj.contract];
73+
}
6274

63-
if (metadataObj) {
75+
if (contractObj) {
6476
// Each field of log needs to appended with '0x' to be parsed
6577
item.address = _utils2.default.appendHexPrefix(item.address);
6678
item.data = _utils2.default.appendHexPrefix(item.data);
6779
item.topics = _lodash2.default.map(item.topics, _utils2.default.appendHexPrefix);
6880

69-
var methodAbi = _lodash2.default.find(metadataObj.abi, { name: eventName });
81+
var methodAbi = _lodash2.default.find(contractObj.abi, { name: eventHashObj.event });
7082
if (_lodash2.default.isUndefined(methodAbi)) {
71-
console.warn('Error: Could not find method in ABI for ' + eventName);
83+
console.warn('Error: Could not find method in ABI for ' + eventHashObj.event);
7284
return;
7385
}
7486

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "qweb3",
33
"namespace": "bodhi",
4-
"version": "0.3.2",
4+
"version": "0.4.0",
55
"description": "Qtum JavaScript API comunicating to qtum node over RPC",
66
"main": "./dist/index.js",
77
"repository": {

src/contract.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class Contract {
8989
}
9090

9191
let dataHex = '';
92-
dataHex = dataHex.concat(Encoder.getFunctionHash(methodObj));
92+
dataHex = dataHex.concat(Encoder.objToHash(methodObj, true));
9393

9494
let hex;
9595
_.each(methodObj.inputs, (item, index) => {

src/encoder.js

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,35 @@ const PADDED_BYTES = 64;
99

1010
class Encoder {
1111
/*
12-
* Converts an object of a method from the ABI to a function hash.
13-
* @param methodObj The json object of the method taken from the ABI.
14-
* @return The function hash.
12+
* Converts an ABI object signature to its hash format.
13+
* @param obj The object of the ABI object.
14+
* @param isFunction Is converting a function object.
15+
* @return The object hash.
1516
*/
16-
static getFunctionHash(methodObj) {
17-
if (!methodObj) {
18-
throw new Error('methodObj should not be undefined');
17+
static objToHash(obj, isFunction) {
18+
if (_.isUndefined(obj)) {
19+
throw new Error('obj should not be undefined');
20+
}
21+
if (_.isUndefined(isFunction)) {
22+
throw new Error('isFunction should not be undefined');
1923
}
2024

21-
const name = methodObj.name;
25+
const name = obj.name;
2226
let params = '';
23-
for (let i = 0; i < methodObj.inputs.length; i++) {
24-
params = params.concat(methodObj.inputs[i].type);
27+
for (let i = 0; i < obj.inputs.length; i++) {
28+
params = params.concat(obj.inputs[i].type);
2529

26-
if (i < methodObj.inputs.length - 1) {
30+
if (i < obj.inputs.length - 1) {
2731
params = params.concat(',');
2832
}
2933
}
30-
const signature = name.concat('(').concat(params).concat(')');
34+
const hash = name.concat(`(${params})`);
3135

32-
// Return only the first 4 bytes
33-
return Web3Utils.sha3(signature).slice(2, 10);
36+
if (isFunction) {
37+
// Return only the first 4 bytes
38+
return Web3Utils.sha3(hash).slice(2, 10);
39+
}
40+
return Web3Utils.sha3(hash).slice(2);
3441
}
3542

3643
/*
@@ -112,7 +119,7 @@ class Encoder {
112119
} else {
113120
bigNum = new BigNumber(num, 10);
114121
}
115-
122+
116123
const hexNum = Web3Utils.numberToHex(bigNum);
117124
return Web3Utils.padLeft(hexNum, PADDED_BYTES).slice(2);
118125
}

src/formatter.js

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import _ from 'lodash';
22
import EthjsAbi from 'ethjs-abi';
33
import Web3Utils from 'web3-utils';
44
import Utils from './utils';
5+
import Encoder from './encoder';
56
import Decoder from './decoder';
67

78
class Formatter {
@@ -13,33 +14,41 @@ class Formatter {
1314
* @return {object} Decoded searchlog output
1415
*/
1516
static searchLogOutput(rawOutput, contractMetadata, removeHexPrefix) {
17+
// Create dict of all event hashes
18+
const eventHashes = {};
19+
_.each(contractMetadata, (contractItem, contractKey) => {
20+
const filteredEvents = _.filter(contractItem.abi, { type: 'event' });
21+
22+
_.each(filteredEvents, (eventObj) => {
23+
const hash = Encoder.objToHash(eventObj, false);
24+
eventHashes[hash] = {
25+
contract: contractKey,
26+
event: eventObj.name,
27+
};
28+
});
29+
});
30+
1631
return _.map(rawOutput, (resultEntry) => {
1732
const formatted = _.assign({}, resultEntry);
1833

1934
if (!_.isEmpty(resultEntry.log)) {
2035
_.each(resultEntry.log, (item, index) => {
21-
const eventHash = item.topics[0];
22-
23-
let eventName;
24-
let metadataObj;
25-
_.each(contractMetadata, (contractItem, index) => {
26-
eventName = (_.invert(contractItem))[eventHash];
36+
const eventHashObj = eventHashes[item.topics[0]];
2737

28-
if (eventName) {
29-
metadataObj = contractItem;
30-
return false;
31-
}
32-
});
38+
let contractObj;
39+
if (eventHashObj) {
40+
contractObj = contractMetadata[eventHashObj.contract];
41+
}
3342

34-
if (metadataObj) {
43+
if (contractObj) {
3544
// Each field of log needs to appended with '0x' to be parsed
3645
item.address = Utils.appendHexPrefix(item.address);
3746
item.data = Utils.appendHexPrefix(item.data);
3847
item.topics = _.map(item.topics, Utils.appendHexPrefix);
3948

40-
const methodAbi = _.find(metadataObj.abi, { name: eventName });
49+
const methodAbi = _.find(contractObj.abi, { name: eventHashObj.event });
4150
if (_.isUndefined(methodAbi)) {
42-
console.warn(`Error: Could not find method in ABI for ${eventName}`);
51+
console.warn(`Error: Could not find method in ABI for ${eventHashObj.event}`);
4352
return;
4453
}
4554

0 commit comments

Comments
 (0)