Added names for nested tuples and fixed topics (now optional) for event parsing.

This commit is contained in:
Richard Moore 2017-11-21 19:24:44 -05:00
parent 807c8133ea
commit 0d106d01af
No known key found for this signature in database
GPG Key ID: 525F70A6FCABC295
8 changed files with 890 additions and 155 deletions

@ -18,6 +18,15 @@ var allowedTransactionKeys = {
data: true, from: true, gasLimit: true, gasPrice:true, to: true, value: true
}
function copyObject(object) {
var result = {};
for (var key in object) {
result[key] = object[key];
}
return result;
}
function Contract(addressOrName, contractInterface, signerOrProvider) {
if (!(this instanceof Contract)) { throw new Error('missing new'); }
@ -57,6 +66,7 @@ function Contract(addressOrName, contractInterface, signerOrProvider) {
if (typeof(transaction) !== 'object') {
throw new Error('invalid transaction overrides');
}
transaction = copyObject(transaction);
// Check for unexpected keys (e.g. using "gas" instead of "gasLimit")
for (var key in transaction) {
@ -150,7 +160,7 @@ function Contract(addressOrName, contractInterface, signerOrProvider) {
if (transaction.nonce) {
noncePromise = Promise.resolve(transaction.nonce)
} else if (signer.getTransactionCount) {
noncePromise = signer.getTransactionCount;
noncePromise = signer.getTransactionCount();
if (!(noncePromise instanceof Promise)) {
noncePromise = Promise.resolve(noncePromise);
}

@ -39,6 +39,7 @@ function defineFrozen(object, name, value) {
});
}
// getKeys([{a: 1, b: 2}, {a: 3, b: 4}], 'a') => [1, 3]
function getKeys(params, key, allowEmpty) {
if (!Array.isArray(params)) { throwError('invalid params', {params: params}); }
@ -58,6 +59,32 @@ function getKeys(params, key, allowEmpty) {
return result;
}
function parseParams(params) {
var names = [];
var types = [];
params.forEach(function(param) {
if (param.components != null) {
if (param.type.substring(0, 5) !== 'tuple') {
throw new Error('internal error; report on GitHub');
}
var suffix = '';
var arrayBracket = param.type.indexOf('[');
if (arrayBracket >= 0) { suffix = param.type.substring(arrayBracket); }
var result = parseParams(param.components);
names.push({ name: (param.name || null), names: result.names });
types.push('tuple(' + result.types.join(',') + ')' + suffix)
} else {
names.push(param.name || null);
types.push(param.type);
}
});
return {
names: names,
types: types
}
}
var coderNull = {
name: 'null',
type: '',
@ -74,9 +101,10 @@ var coderNull = {
dynamic: false
};
function coderNumber(size, signed) {
function coderNumber(size, signed, localName) {
var name = ((signed ? 'int': 'uint') + size);
return {
localName: localName,
name: name,
type: name,
encode: function(value) {
@ -107,24 +135,28 @@ function coderNumber(size, signed) {
}
var uint256Coder = coderNumber(32, false);
var coderBoolean = {
name: 'boolean',
type: 'boolean',
encode: function(value) {
return uint256Coder.encode(value ? 1: 0);
},
decode: function(data, offset) {
var result = uint256Coder.decode(data, offset);
return {
consumed: result.consumed,
value: !result.value.isZero()
var coderBoolean = function(localName) {
return {
localName: localName,
name: 'boolean',
type: 'boolean',
encode: function(value) {
return uint256Coder.encode(value ? 1: 0);
},
decode: function(data, offset) {
var result = uint256Coder.decode(data, offset);
return {
consumed: result.consumed,
value: !result.value.isZero()
}
}
}
}
function coderFixedBytes(length) {
function coderFixedBytes(length, localName) {
var name = ('bytes' + length);
return {
localName: localName,
name: name,
type: name,
encode: function(value) {
@ -146,20 +178,23 @@ function coderFixedBytes(length) {
};
}
var coderAddress = {
name: 'address',
type: 'address',
encode: function(value) {
value = utils.arrayify(utils.getAddress(value));
var result = new Uint8Array(32);
result.set(value, 12);
return result;
},
decode: function(data, offset) {
if (data.length < offset + 32) { throwError('invalid address'); }
return {
consumed: 32,
value: utils.getAddress(utils.hexlify(data.slice(offset + 12, offset + 32)))
var coderAddress = function(localName) {
return {
localName: localName,
name: 'address',
type: 'address',
encode: function(value) {
value = utils.arrayify(utils.getAddress(value));
var result = new Uint8Array(32);
result.set(value, 12);
return result;
},
decode: function(data, offset) {
if (data.length < offset + 32) { throwError('invalid address'); }
return {
consumed: 32,
value: utils.getAddress(utils.hexlify(data.slice(offset + 12, offset + 32)))
}
}
}
}
@ -188,33 +223,39 @@ function _decodeDynamicBytes(data, offset) {
}
}
var coderDynamicBytes = {
name: 'bytes',
type: 'bytes',
encode: function(value) {
return _encodeDynamicBytes(utils.arrayify(value));
},
decode: function(data, offset) {
var result = _decodeDynamicBytes(data, offset);
result.value = utils.hexlify(result.value);
return result;
},
dynamic: true
};
var coderDynamicBytes = function(localName) {
return {
localName: localName,
name: 'bytes',
type: 'bytes',
encode: function(value) {
return _encodeDynamicBytes(utils.arrayify(value));
},
decode: function(data, offset) {
var result = _decodeDynamicBytes(data, offset);
result.value = utils.hexlify(result.value);
return result;
},
dynamic: true
};
}
var coderString = {
name: 'string',
type: 'string',
encode: function(value) {
return _encodeDynamicBytes(utils.toUtf8Bytes(value));
},
decode: function(data, offset) {
var result = _decodeDynamicBytes(data, offset);
result.value = utils.toUtf8String(result.value);
return result;
},
dynamic: true
};
var coderString = function(localName) {
return {
localName: localName,
name: 'string',
type: 'string',
encode: function(value) {
return _encodeDynamicBytes(utils.toUtf8Bytes(value));
},
decode: function(data, offset) {
var result = _decodeDynamicBytes(data, offset);
result.value = utils.toUtf8String(result.value);
return result;
},
dynamic: true
};
}
function alignSize(size) {
return parseInt(32 * Math.ceil(size / 32));
@ -259,12 +300,10 @@ function pack(coders, values) {
return data;
}
function unpack(coders, data, offset) {
var baseOffset = offset;
var consumed = 0;
var value = [];
coders.forEach(function(coder) {
if (coder.dynamic) {
var dynamicOffset = uint256Coder.decode(data, offset);
@ -283,6 +322,20 @@ function unpack(coders, data, offset) {
consumed += result.consumed;
});
coders.forEach(function(coder, index) {
var name = coder.localName;
if (!name) { return; }
if (typeof(name) === 'object') { name = name.name; }
if (!name) { return; }
if (name === 'length') { name = '_length'; }
if (value[name] != null) { return; }
value[name] = value[index];
});
return {
value: value,
consumed: consumed
@ -291,11 +344,12 @@ function unpack(coders, data, offset) {
return result;
}
function coderArray(coder, length) {
function coderArray(coder, length, localName) {
var type = (coder.type + '[' + (length >= 0 ? length: '') + ']');
return {
coder: coder,
localName: localName,
length: length,
name: 'array',
type: type,
@ -344,7 +398,7 @@ function coderArray(coder, length) {
}
function coderTuple(coders) {
function coderTuple(coders, localName) {
var dynamic = false;
var types = [];
coders.forEach(function(coder) {
@ -356,12 +410,19 @@ function coderTuple(coders) {
return {
coders: coders,
localName: localName,
name: 'tuple',
type: type,
encode: function(value) {
if (Array.isArray(value)) {
if (coders.length !== value.length) {
throwError('types/values mismatch', { type: type, values: values });
}
if (coders.length !== coders.length) {
throwError('types/values mismatch', { type: type, values: values });
// @TODO: If receiving an object, and we have names, create the array
} else {
throwError('invalid value', { type: types, values: values });
}
return pack(coders, value);
@ -414,10 +475,9 @@ var paramTypeSimple = {
bytes: coderDynamicBytes,
};
function getParamCoder(type) {
function getParamCoder(type, localName) {
var coder = paramTypeSimple[type];
if (coder) { return coder; }
if (coder) { return coder(localName); }
var match = type.match(paramTypeNumber);
if (match) {
@ -425,7 +485,7 @@ function getParamCoder(type) {
if (size === 0 || size > 256 || (size % 8) !== 0) {
throwError('invalid type', { type: type });
}
return coderNumber(size / 8, (match[1] === 'int'));
return coderNumber(size / 8, (match[1] === 'int'), localName);
}
var match = type.match(paramTypeBytes);
@ -434,21 +494,26 @@ function getParamCoder(type) {
if (size === 0 || size > 32) {
throwError('invalid type ' + type);
}
return coderFixedBytes(size);
return coderFixedBytes(size, localName);
}
var match = type.match(paramTypeArray);
if (match) {
var size = parseInt(match[2] || -1);
return coderArray(getParamCoder(match[1]), size);
return coderArray(getParamCoder(match[1], localName), size, localName);
}
if (type.substring(0, 6) === 'tuple(' && type.substring(type.length - 1) === ')') {
var coders = [];
splitNesting(type.substring(6, type.length - 1)).forEach(function(type) {
coders.push(getParamCoder(type));
var names = [];
if (localName && typeof(localName) === 'object') {
if (Array.isArray(localName.names)) { names = localName.names; }
if (typeof(localName.name) === 'string') { localName = localName.name; }
}
splitNesting(type.substring(6, type.length - 1)).forEach(function(type, index) {
coders.push(getParamCoder(type, names[index]));
});
return coderTuple(coders);
return coderTuple(coders, localName);
}
if (type === '') {
@ -478,6 +543,11 @@ utils.defineProperty(TransactionDescription.prototype, 'type', 'transaction');
function EventDescription() { }
utils.defineProperty(EventDescription.prototype, 'type', 'event');
function Indexed(value) {
utils.defineProperty(this, 'indexed', true);
utils.defineProperty(this, 'hash', value);
}
function Interface(abi) {
if (!(this instanceof Interface)) { throw new Error('missing new'); }
@ -502,6 +572,7 @@ function Interface(abi) {
switch (method.type) {
case 'constructor':
var func = (function() {
// @TODO: Move to parseParams
var inputTypes = getKeys(method.inputs, 'type');
var func = function(bytecode) {
if (!utils.isHexString(bytecode)) {
@ -522,6 +593,7 @@ function Interface(abi) {
return populateDescription(new DeployDescription(), result);
}
// @TODO: Move to parseParams
defineFrozen(func, 'inputs', getKeys(method.inputs, 'name'));
return func;
@ -533,13 +605,16 @@ function Interface(abi) {
case 'function':
var func = (function() {
var inputTypes = getKeys(method.inputs, 'type');
var inputParams = parseParams(method.inputs);
var outputParams = parseParams(method.outputs);
var inputTypes = inputParams.types;
if (method.constant) {
var outputTypes = getKeys(method.outputs, 'type');
var outputNames = getKeys(method.outputs, 'name', true);
var outputTypes = outputParams.types;
var outputNames = outputParams.names;
}
var signature = method.name + '(' + getKeys(method.inputs, 'type').join(',') + ')';
var signature = method.name + '(' + inputParams.types.join(',') + ')';
var sighash = utils.keccak256(utils.toUtf8Bytes(signature)).substring(0, 10);
var func = function() {
var result = {
@ -571,6 +646,7 @@ function Interface(abi) {
return populateDescription(new TransactionDescription(), result);
}
// @TODO: Move the paraseParams
defineFrozen(func, 'inputs', getKeys(method.inputs, 'name'));
defineFrozen(func, 'outputs', getKeys(method.outputs, 'name'));
utils.defineProperty(func, 'signature', signature);
@ -589,8 +665,10 @@ function Interface(abi) {
case 'event':
var func = (function() {
// @TODO: Move to parseParams
var inputTypes = getKeys(method.inputs, 'type');
var func = function() {
// @TODO: Move to parseParams
var signature = method.name + '(' + getKeys(method.inputs, 'type').join(',') + ')';
var result = {
inputs: method.inputs,
@ -600,27 +678,41 @@ function Interface(abi) {
};
result.parse = function(topics, data) {
if (data == null) {
data = topics;
topics = null;
}
// Strip the signature off of non-anonymous topics
if (!method.anonymous) { topics = topics.slice(1); }
if (topics != null && !method.anonymous) { topics = topics.slice(1); }
var inputNamesIndexed = [], inputNamesNonIndexed = [];
var inputTypesIndexed = [], inputTypesNonIndexed = [];
var inputDynamic = [];
method.inputs.forEach(function(input) {
if (input.indexed) {
if (input.type === 'string' || input.type === 'bytes' || input.type.indexOf('[') >= 0) {
inputTypesIndexed.push('bytes32');
inputDynamic.push(true);
} else {
inputTypesIndexed.push(input.type);
inputDynamic.push(false);
}
inputNamesIndexed.push(input.name);
inputTypesIndexed.push(input.type);
} else {
inputNamesNonIndexed.push(input.name);
inputTypesNonIndexed.push(input.type);
inputDynamic.push(false);
}
});
var resultIndexed = Interface.decodeParams(
inputNamesIndexed,
inputTypesIndexed,
utils.concat(topics)
);
if (topics != null) {
var resultIndexed = Interface.decodeParams(
inputNamesIndexed,
inputTypesIndexed,
utils.concat(topics)
);
}
var resultNonIndexed = Interface.decodeParams(
inputNamesNonIndexed,
@ -632,7 +724,19 @@ function Interface(abi) {
var nonIndexedIndex = 0, indexedIndex = 0;
method.inputs.forEach(function(input, i) {
if (input.indexed) {
result[i] = resultIndexed[indexedIndex++];
if (topics == null) {
result[i] = new Indexed(null);
} else if (inputDynamic[i]) {
result[i] = new Indexed(resultIndexed[indexedIndex++]);
/*{
indexed: true,
hash: resultIndexed[indexedIndex++]
};
*/
} else {
result[i] = resultIndexed[indexedIndex++];
}
} else {
result[i] = resultNonIndexed[nonIndexedIndex++];
}
@ -646,6 +750,7 @@ function Interface(abi) {
return populateDescription(new EventDescription(), result);
}
// @TODO: Move to parseParams
defineFrozen(func, 'inputs', getKeys(method.inputs, 'name'));
return func;
@ -698,39 +803,18 @@ utils.defineProperty(Interface, 'decodeParams', function(names, types, data) {
if (arguments.length < 3) {
data = types;
types = names;
names = [];
names = null;
}
data = utils.arrayify(data);
var coders = [];
types.forEach(function(type) {
coders.push(getParamCoder(type));
types.forEach(function(type, index) {
coders.push(getParamCoder(type, (names ? names[index]: undefined)));
});
var result = coderTuple(coders).decode(data, 0);
// @TODO: Move this into coderTuple
var values = new Result();
coders.forEach(function(coder, index) {
values[index] = result.value[index];
if (names && names[index]) {
var name = names[index];
if (name === 'length') {
console.log('WARNING: result length renamed to _length');
name = '_length';
}
if (values[name] == null) {
values[name] = values[index];
} else {
console.log('WARNING: duplicate value - ' + name);
}
}
})
values.length = types.length;
return values;
return coderTuple(coders).decode(data, 0).value;
});
module.exports = Interface;

@ -1,6 +1,6 @@
{
"name": "ethers-contracts",
"version": "2.1.5",
"version": "2.1.6",
"description": "Contract and Interface (ABI) library for Ethereum.",
"bugs": {
"url": "http://github.com/ethers-io/ethers.js/issues",

@ -0,0 +1,574 @@
'use strict';
var crypto = require('crypto');
var promiseRationing = require('promise-rationing');
var Web3 = require('web3');
var web3 = new Web3(new Web3.providers.HttpProvider('http://127.0.0.1:8549'));
var BN = require('bn.js');
var web3Coder = require('web3/lib/solidity/coder');
var getAddress = require('../../utils/address.js').getAddress;
var keccak256 = require('../../utils/keccak256');
var utils = require('../utils');
process.on('unhandledRejection', function(reason, p){
console.log('Error: Unhandled promise rejection');
console.log(reason);
});
var compile = (function() {
var soljson = require('../soljson.js');
var _compile = soljson.cwrap("compileJSONCallback", "string", ["string", "number", "number"]);
function compile(source) {
return JSON.parse(_compile(JSON.stringify({sources: { "demo.sol": source }}), 0));
}
compile.version = JSON.parse(compile('contract Foo { }').contracts['demo.sol:Foo'].metadata).compiler.v$
return compile;
})();
function web3Promise(method, params) {
return new Promise(function(resolve, reject) {
params.push(function(error, result) {
if (error) {
console.log(error);
return reject(error);
}
resolve(result);
});
web3.eth[method].apply(web3, params);
});
}
function sendTransaction(transaction) {
var address = '0x00Bd138aBD70e2F00903268F3Db08f2D25677C9e';
transaction.from = address;
//console.log('Sending...');
return Promise.all([
web3Promise('getGasPrice', []),
web3Promise('getTransactionCount', [address, 'pending'])
]).then(function(result) {
transaction.gasPrice = '0x' + result[0].toString(16);
transaction.gas = "0x55d4a80";
return web3Promise('sendTransaction', [transaction]);
});
}
function waitForTransaction(hash) {
return new Promise(function(resolve, reject) {
function check() {
web3Promise('getTransactionReceipt', [hash]).then(function(transaction) {
if (transaction && transaction.blockHash) {
resolve(transaction);
return;
}
setTimeout(check, 1000);
}, function(error) {
reject(error);
});
}
check();
});
}
// Create the indent given a tabstop
function indent(tabs) {
var indent = new Buffer(tabs * 4);
indent.fill(32);
return indent.toString('utf8')
}
function id(text) {
return crypto.createHash('sha256').update(text).digest().toString('hex').substring(0, 10).toUpperCase();
}
function getEventName(types) {
return 'testEvent';
}
function createContractSource(test, comments) {
var events = '';
var source = '';
var types = [];
var named = [];
var values = [];
var returns = [];
test.params.forEach(function(param, index) {
types.push(param.type);
named.push(param.type + (param.indexed ? ' indexed': '') + ' p' + index);
if (param.type === 'string') {
source += (indent(2) + 's' + index + ' = "' + param.value + '";\n');
} else if (param.type === 'bytes') {
var value = new Buffer(param.value.substring(2), 'hex');
source += indent(2) + 's' + index + ' = new bytes(' + value.length + ');\n';
source += indent(2) + 'assembly {\n';
source += indent(3) + 'mstore(s' + index + ', ' + value.length + ')\n';
for (var i = 0; i < value.length; i++) {
source += indent(3) + 'mstore8(add(s' + index + ', ' + (32 + i) + '), ' + value[i] + ')\n';
}
source += indent(2) + '}\n'
} else if (param.type.indexOf('[') >= 0) {
if (param.type.substring(param.type.length - 2) === '[]') {
source += indent(2) + 's' + index + ' = new ' + param.type + '(' + param.value.length + ');\n';
}
var baseType = param.type.substring(0, param.type.indexOf('['));
function dump(prefix, value) {
if (Array.isArray(value)) {
value.forEach(function(value, index) {
dump(prefix + '[' + index + ']', value);
});
} else {
source += indent(2) + prefix + ' = ' + baseType + '(' + value + ');\n';
}
}
dump('s' + index, param.value);
} else {
source += (
indent(2) +
's' + index + ' = ' +
param.type + '(' + param.value + ');\n'
);
}
returns.push(param.type + ' s' + index);
values.push('s' + index);
});
events += (
indent(1) +
'event ' +
getEventName(types) +
'(' + named.join(', ') + ')' +
(test.anonymous ? ' anonymous': '') +
';\n'
);
source += indent(2) + getEventName(types) + '(' + values.join(', ') + ');\n';
var sourceInit = '';
var signature = 'function test() public returns (' + returns.join(', ') + ') {\n';
if (returns.length === 0) {
signature = 'function test() public {\n';
}
var sourceComments = '';
comments.forEach(function(comment) {
sourceComments += '// ' + comment + '\n';
});
if (sourceComments.length) { sourceComments += '\n'; }
return [
sourceComments,
'contract Test {\n',
events,
'\n',
(indent(1) + signature),
sourceInit,
source,
(indent(1) + '}\n'),
'}\n'
].join('')
}
function isHashed(value) {
return (value === 'string' || value === 'bytes' || value.indexOf('[') >= 0);
}
function makeTests() {
var tests = [];
tests.push({
name: 'simple-1',
params: [
{ type: 'address', value: '0x0123456789012345678901234567890123456789' }
]
});
tests.push({
name: 'simple-2',
params: [
{ indexed: true, type: 'address', value: '0x0123456789012345678901234567890123456789' }
]
});
tests.push({
name: 'simple-3',
anonymous: true,
params: [
{ type: 'address', value: '0x0123456789012345678901234567890123456789' }
]
});
tests.push({
name: 'simple-4',
anonymous: true,
params: [
{ indexed: true, type: 'address', value: '0x0123456789012345678901234567890123456789' }
]
});
tests.push({
name: 'mixed',
params: [
{ indexed: true, type: 'uint256', value: '0x0123' },
{ indexed: false, type: 'uint256', value: '0x5678' },
{ indexed: true, type: 'uint256', value: '0x9012' },
{ indexed: true, type: 'uint256', value: '0x3456' },
]
});
tests.push({
name: 'string',
params: [
{ type: 'string', value: 'Hello World' }
]
});
tests.push({
name: 'string-indexed',
params: [
{ indexed: true, type: 'string', value: 'Hello World' }
]
});
tests.push({
name: 'bytes',
params: [
{ type: 'bytes', value: '0x314159' }
]
});
tests.push({
name: 'bytes-indexed',
params: [
{ indexed: true, type: 'bytes', value: '0x314159' }
]
});
tests.push({
name: 'array',
params: [
{ type: 'uint256[3]', value: ['0x31', '0x41', '0x59' ] }
]
});
tests.push({
name: 'array-indexed',
params: [
{ indexed: true, type: 'uint256[3]', value: ['0x31', '0x41', '0x59' ] }
]
});
tests.push({
name: 'array-2d',
params: [
{ type: 'uint256[2][3]', value: [
['0x31', '0x41'],
['0x87', '0x65'],
['0x12', '0x19'],
] }
]
});
tests.push({
name: 'array-2d-indexed',
params: [
{ indexed: true, type: 'uint256[2][3]', value: [
['0x31', '0x41'],
['0x87', '0x65'],
['0x12', '0x19'],
] }
]
});
tests.push({
name: 'array-dynamic',
params: [
{ type: 'uint256[2][]', value: [
['0x31', '0x41'],
['0x87', '0x65'],
['0x12', '0x19'],
['0x99', '0x88'],
] }
]
});
tests.push({
name: 'array-dynamic-indexed',
params: [
{ indexed: true, type: 'uint256[2][]', value: [
['0x31', '0x41'],
['0x87', '0x65'],
['0x12', '0x19'],
['0x99', '0x88'],
] }
]
});
function generate(seed, onlyStatic) {
switch (utils.randomNumber(seed + '-type', 0, (onlyStatic ? 3: 6))) {
case 0:
return {
type: 'address',
value: function(extra) {
return utils.randomHexString(seed + '-address-' + extra, 20, 20);
}
};
case 1:
var sign = (utils.randomNumber(seed + '-numberSign', 0, 2) == 0);
var type = ((sign ? '': 'u') + 'int');
var size = utils.randomNumber(seed + '-numberSize', 0, 33) * 8;
if (size !== 0) {
type += String(size);
} else {
size = 256;
}
return {
type: type,
value: function(extra) {
var value = new BN(utils.randomHexString(seed + '-numberValue-' + extra, 1, size / 8).substring(2), 16);
if (sign) {
var signBit = (new BN(1)).shln(size - 1);
if (!signBit.and(value).isZero()) {
value = value.maskn(size - 1).mul(new BN(-1));
}
}
if (value.isNeg()) {
return '-0x' + value.toString('hex').substring(1);
}
return '0x' + value.toString('hex');
}
}
case 2:
var count = utils.randomNumber(seed + '-bytesCount', 1, 33);
return {
type: 'bytes' + String(count),
value: function(extra) {
return utils.randomHexString(seed + '-bytesValue-' + extra, count, count);
}
};
case 3:
var longText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
return {
type: 'string',
value: function(extra) {
return longText.substring(0, utils.randomNumber(seed + '-string-' + extra, 0, longText.length));
}
};
case 4:
return {
type: 'bytes',
value: function(extra) {
return utils.randomHexString(seed + '-bytes-' + extra, 0, 67);
}
}
case 5:
var kind = generate(seed + '-arrayKind', true);
var depth = utils.randomNumber(seed + '-arrayDepth', 1, 4);
var sizes = [];
for (var i = 0; i < depth; i++) {
// Letting this lowerbound be 0 crashes the compiler...
sizes.push(utils.randomNumber(seed + '-arraySize-' + i, 1, 4));
}
var suffix = '';
sizes.forEach(function(size, index) {
if (index === 0 && utils.randomNumber(seed + 'arrayDynamic', 0, 2) === 1) {
suffix = '[]' + suffix;
} else {
suffix = '[' + size + ']' + suffix;
}
});
return {
type: kind.type + suffix,
value: function(extra) {
function dump(sizes, extra) {
var result = [];
for (var i = 0; i < sizes[0]; i++) {
if (sizes.length === 1) {
result.push(kind.value(extra + '-' + i));
} else {
result.push(dump(sizes.slice(1), extra + '-' + sizes[0] + '-' + i));
}
}
return result;
}
return dump(sizes, '-value-' + extra);
}
}
}
}
for (var i = 0; i < 2000; i++) {
var name = 'random-' + i;
var seed = name;
var params = [];
var count = utils.randomNumber(seed + '-count', 0, 7);
for (var j = 0; j < count; j++) {
var generator = generate(seed + '-param-' + j)
var param = generator;
param.value = generator.value('-generated-' + j);
params.push(param);
}
// May collide; not a huge deal and much easier than being perfectly uniform
var indexedCount = utils.randomNumber(seed + '-indexedCount', 0, 3);
if (indexedCount > count) { indexedCount = count; }
for (var j = 0; j < indexedCount; j++) {
var index = utils.randomNumber(seed + '-indexed-' + j, 0, count);
//console.log(params, index);
params[index].indexed = true;
}
tests.push({
name: name,
anonymous: ((utils.randomNumber(seed + '-anonymous', 0, 2) === 0) ? false: true),
params: params
});
}
var promiseFuncs = [];
tests.forEach(function(test) {
promiseFuncs.push(function(resolve, reject) {
var source = createContractSource(test, [ ('Test: ' + test.name), JSON.stringify(test) ]);
console.log(source);
var contracts = compile(source);
if (!contracts || !contracts.contracts || !contracts.contracts['demo.sol:Test']) {
console.log(contracts);
console.log(test);
console.log(source);
process.exit();
}
var contract = contracts.contracts['demo.sol:Test'];
//console.log(contract);
var tx = { data: ('0x' + contract.bytecode) };
return sendTransaction(tx).then(function(hash) {
return waitForTransaction(hash);
}).then(function(tx) {
return sendTransaction({
to: tx.contractAddress,
data: '0xf8a8fd6d'
}).then(function(hash) {
return waitForTransaction(hash);
});
}).then(function(tx) {
if (tx.logs.length !== 1) {
console.log('What?', tx);
process.exit(1);
}
var types = [];
var values = [];
var hashed = [];
var indexed = [];
var normalizedValues = [];
test.params.forEach(function(param) {
types.push(param.type);
indexed.push(param.indexed);
if (param.indexed && isHashed(param.type)) {
hashed.push(true);
if (param.type === 'string') {
normalizedValues.push(keccak256(new Buffer(param.value, 'utf8')));
} else if (param.type === 'bytes') {
normalizedValues.push(keccak256(new Buffer(param.value.substring(2), 'hex')));
} else if (param.type.indexOf('[') >= 0) {
var compute = param.type;
if (compute.substring(compute.length - 2) === '[]') {
compute = compute.substring(0, compute.length - 2);
compute += '[' + param.value.length + ']';
}
var baseType = compute.substring(0, compute.indexOf('['));
function dump(input) {
if (Array.isArray(input)) {
var result = [];
input.forEach(function(i) {
result.push(dump(i));
});
return result;
}
if (baseType === 'address') {
return input;
}
if (baseType.substring(0, 5) === 'bytes') {
return input;
}
return web3.toBigNumber(input);
}
var web3Value = dump(param.value);
// The web3 coder has lots of bugs, but it does fine as long as there
// is only one type and nothing is dynamic
var encoded = web3Coder.encodeParams([ compute ], [ web3Value ]);
normalizedValues.push(keccak256(new Buffer(encoded, 'hex')));
} else {
throw new Error('unknown hashed type');
}
} else {
hashed.push(false);
normalizedValues.push(param.value);
}
values.push(param.value);
});
resolve({
bytecode: '0x' + contract.bytecode,
data: tx.logs[0].data,
hashed: hashed,
indexed: indexed,
interface: contract.interface,
name: test.name,
source: source,
topics: tx.logs[0].topics,
types: types,
normalizedValues: normalizedValues,
values: values
});
}).catch(function(error) {
console.log('TTT', test);
reject(error);
});
});
});
promiseRationing.all(promiseFuncs, 40).then(function(results) {
console.log('complete', results);
utils.saveTests('contract-events', results);
}, function(error) {
console.log(error);
});
}
makeTests();

@ -347,6 +347,22 @@ function testContracts(test) {
test.equal(results[9][0], 'Cherry', 'getMapping(C)');
test.equal(results[10][0], '', 'getMapping(Nothing)');
test.ok(results[0].value.eq(42), 'named getUintValue()');
test.equal(results[1].value, 'One', 'named getArrayValue(1)');
test.equal(results[2].value, 'Two', 'named getArrayValue(2)');
test.equal(results[3].value, 'Three', 'named getArrayValue(3)');
test.equal(
utils.hexlify(results[4].value),
utils.keccak256(utils.toUtf8Bytes('TheEmptyString')),
'named getBytes32Value()'
);
test.equal(results[5].value, 'This is not a string.', 'named getStringValue()');
test.equal(results[6].value, TestContract.owner, 'named getOwner()');
test.equal(results[7].value, 'Apple', 'named getMapping(A)');
test.equal(results[8].value, 'Banana', 'named getMapping(B)');
test.equal(results[9].value, 'Cherry', 'named getMapping(C)');
test.equal(results[10].value, '', 'named getMapping(Nothing)');
var getValue = results[11][0];
var onvaluechanged = results[12];
@ -422,7 +438,7 @@ function testENSProviderReadOnly(test, provider) {
});
Promise.all(promises).then(function(results) {
console.log(results);
//console.log(results);
test.done();
}, function(error) {
console.log(error);

@ -24,64 +24,74 @@ if (testEthereumLib) {
ethereumLibCoder = require('ethereumjs-abi');
}
function equals(a, b) {
function equals(actual, expected) {
// Array (treat recursively)
if (Array.isArray(a)) {
if (!Array.isArray(b) || a.length !== b.length) { return false; }
for (var i = 0; i < a.length; i++) {
if (!equals(a[i], b[i])) { return false; }
if (Array.isArray(actual)) {
if (!Array.isArray(expected) || actual.length !== expected.length) { return false; }
for (var i = 0; i < actual.length; i++) {
if (!equals(actual[i], expected[i])) { return false; }
}
return true;
}
if (testWeb3) {
if (a.toJSON && a.minus) { a = utils.bigNumberify(a.toString(10)); }
if (b.toJSON && b.minus) { b = utils.bigNumberify(b.toString(10)); }
if (actual.toJSON && actual.minus) { actual = utils.bigNumberify(actual.toString(10)); }
if (expected.toJSON && expected.minus) { expected = utils.bigNumberify(expected.toString(10)); }
}
// EthereumLib returns addresses as 160-bit big numbers
if (testEthereumLib) {
if (a.match && a.match(/^0x[0-9A-Fa-f]{40}$/) && b.imul) {
a = utils.bigNumberify(a);
if (actual.match && actual.match(/^0x[0-9A-Fa-f]{40}$/) && expected.imul) {
actual = utils.bigNumberify(actual);
}
if (b.match && b.match(/^0x[0-9A-Fa-f]{40}$/) && a.imul) {
b = utils.bigNumberify(a);
if (expected.match && expected.match(/^0x[0-9A-Fa-f]{40}$/) && actual.imul) {
expected = utils.bigNumberify(actual);
}
}
if (typeof(actual) === 'number') { actual = utils.bigNumberify(actual); }
if (typeof(expected) === 'number') { expected = utils.bigNumberify(expected); }
// BigNumber
if (a.eq) {
if (!a.eq(b)) { return false; }
if (actual.eq) {
if (typeof(expected) === 'string' && expected.match(/^-?0x[0-9A-Fa-f]*$/)) {
var neg = (expected.substring(0, 1) === '-');
if (neg) { expected = expected.substring(1); }
expected = utils.bigNumberify(expected);
if (neg) { expected = expected.mul(-1); }
}
if (!actual.eq(expected)) { return false; }
return true;
}
if (testEthereumLib) {
if (a.buffer) { a = '0x' + (new Buffer(a)).toString('hex'); }
if (b.buffer) { b = '0x' + (new Buffer(b)).toString('hex'); }
if (Buffer.isBuffer(a)) { a = '0x' + a.toString('hex'); }
if (Buffer.isBuffer(b)) { b = '0x' + b.toString('hex'); }
if (actual.buffer) { a = '0x' + (new Buffer(actual)).toString('hex'); }
if (expected.buffer) { b = '0x' + (new Buffer(expected)).toString('hex'); }
if (Buffer.isBuffer(actual)) { actual = '0x' + actual.toString('hex'); }
if (Buffer.isBuffer(expected)) { expected = '0x' + expected.toString('hex'); }
}
// Uint8Array
if (a.buffer) {
if (!utils.isHexString(b)) { return false; }
b = utils.arrayify(b);
if (expected.buffer) {
if (!utils.isHexString(actual)) { return false; }
actual = utils.arrayify(actual);
if (!b.buffer || a.length !== b.length) { return false; }
for (var i = 0; i < a.length; i++) {
if (a[i] !== b[i]) { return false; }
if (!actual.buffer || actual.length !== expected.length) { return false; }
for (var i = 0; i < actual.length; i++) {
if (actual[i] !== expected[i]) { return false; }
}
return true;
}
if (testWeb3 || true) {
if (a.match && a.match(/^0x[0-9A-Fa-f]{40}$/)) { a = a.toLowerCase(); }
if (b.match && b.match(/^0x[0-9A-Fa-f]{40}$/)) { b = b.toLowerCase(); }
if (actual.match && actual.match(/^0x[0-9A-Fa-f]{40}$/)) { actual = actual.toLowerCase(); }
if (expected.match && expected.match(/^0x[0-9A-Fa-f]{40}$/)) { expected = expected.toLowerCase(); }
}
// Something else
return a === b;
return (actual === expected);
}
@ -171,9 +181,9 @@ describe('Contract Interface ABI Decoding', function() {
it(('decodes parameters - ' + test.name + ' - ' + test.types), function() {
var decoded = Interface.decodeParams(types, result);
var decodedArray = Array.prototype.slice.call(decoded);;
//var decodedArray = Array.prototype.slice.call(decoded);;
assert.ok(equals(values, decodedArray), 'decoded parameters - ' + title);
assert.ok(equals(decoded, values), 'decoded parameters - ' + title);
if (testWeb3) {
var badWeb3 = [ 'random-1116' ];
@ -232,11 +242,9 @@ describe('Contract Interface ABI v2 Decoding', function() {
var result = test.result;
var title = test.name + ' => (' + test.types + ') = (' + test.values + ')';
it(('decodes parameters - ' + test.name + ' - ' + test.types), function() {
it(('decodes ABIv2 parameters - ' + test.name + ' - ' + test.types), function() {
var decoded = Interface.decodeParams(types, result);
var decodedArray = Array.prototype.slice.call(decoded);
assert.ok(equals(values, decodedArray), 'decoded parameters - ' + title);
assert.ok(equals(decoded, values), 'decoded parameters - ' + title);
});
});
});
@ -253,20 +261,63 @@ describe('Contract Interface ABI v2 Encoding', function() {
it(('encodes parameters - ' + test.name + ' - ' + test.types), function() {
var encoded = Interface.encodeParams(types, values);
/*
console.log('Actual:');
for (var i = 2; i < encoded.length; i += 64) {
console.log(' ', encoded.substring(i, i + 64));
}
console.log('Expected:');
for (var i = 2; i < expected.length; i += 64) {
console.log(' ', expected.substring(i, i + 64));
}
*/
assert.equal(encoded, expected, 'decoded parameters - ' + title);
});
});
});
describe('Contract Interface ABI v2 Named Decoding', function() {
var Interface = require('../contracts').Interface;
var name = "random-1939";
var abi = [{"constant":true,"inputs":[],"name":"test","outputs":[{"name":"r0","type":"bytes"},{"components":[{"name":"a","type":"address"},{"components":[{"name":"a","type":"uint168"}],"name":"b","type":"tuple"},{"components":[{"components":[{"name":"a","type":"int40"},{"name":"b","type":"bytes"}],"name":"a","type":"tuple"}],"name":"c","type":"tuple"}],"name":"r1","type":"tuple[2]"}],"payable":false,"stateMutability":"pure","type":"function"}];
var result = '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000001c3b89443bbb68b4c937b8b3a3b973b271eb657a2fd8615546c73c3ee50000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000160000000000000000000000000b446da3c1c4a8418ce5f6201bcceee5069ac73dd0000000000000000000000000000db730a804e1e646d9b132cf899440de4a09a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000c018ec00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000032b26b4052e530418d47dfde4b67e735e1818a03098ddbb26b050f18961016299e9374f2dea29763411dcb9213c13040a2cc010000000000000000000000000000000000000000000000000000b446da3c1c4a8418ce5f6201bcceee5069ac73dd0000000000000000000000000000db730a804e1e646d9b132cf899440de4a09a000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000c018ec00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000032b26b4052e530418d47dfde4b67e735e1818a03098ddbb26b050f18961016299e9374f2dea29763411dcb9213c13040a2cc010000000000000000000000000000';
var types = ["bytes","tuple(address,tuple(uint168),tuple(tuple(int40,bytes)))[2]"]
var values = [{"type":"buffer","value":"0x3b89443bbb68b4c937b8b3a3b973b271eb657a2fd8615546c73c3ee5"},[{"type":"tuple","value":[{"type":"string","value":"0xB446dA3C1c4a8418Ce5f6201bCceee5069Ac73DD"},{"type":"tuple","value":[{"type":"number","value":"19116737049729793555492062790071228687491226"}]},{"type":"tuple","value":[{"type":"tuple","value":[{"type":"number","value":"12589292"},{"type":"buffer","value":"0xb26b4052e530418d47dfde4b67e735e1818a03098ddbb26b050f18961016299e9374f2dea29763411dcb9213c13040a2cc01"}]}]}]},{"type":"tuple","value":[{"type":"string","value":"0xB446dA3C1c4a8418Ce5f6201bCceee5069Ac73DD"},{"type":"tuple","value":[{"type":"number","value":"19116737049729793555492062790071228687491226"}]},{"type":"tuple","value":[{"type":"tuple","value":[{"type":"number","value":"12589292"},{"type":"buffer","value":"0xb26b4052e530418d47dfde4b67e735e1818a03098ddbb26b050f18961016299e9374f2dea29763411dcb9213c13040a2cc01"}]}]}]}]];
it('decodes exporting named values', function() {
var iface = new Interface(abi);
var info = iface.functions.test();
var decoded = info.parse(result);
//console.dir(decoded, { depth: null });
});
});
describe('Test Contract Events', function() {
var Interface = require('../contracts').Interface;
var tests = utils.loadTests('contract-events');
tests.forEach(function(test, index) {
it(('decodes event parameters - ' + test.name + ' - ' + test.types), function() {
var contract = new Interface(test.interface);
var event = contract.events.testEvent();
var parsed = event.parse(test.topics, test.data);
test.normalizedValues.forEach(function(expected, index) {
if (test.hashed[index]) {
assert.ok(equals(parsed[index].hash, expected), 'parsed event indexed parameter matches - ' + index);
} else {
assert.ok(equals(parsed[index], expected), 'parsed event parameter matches - ' + index);
}
});
});
});
tests.forEach(function(test, index) {
it(('decodes event data - ' + test.name + ' - ' + test.types), function() {
var contract = new Interface(test.interface);
var event = contract.events.testEvent();
var parsed = event.parse(test.data);
test.normalizedValues.forEach(function(expected, index) {
if (test.indexed[index]) {
assert.ok((parsed[index].indexed && parsed[index].hash == null), 'parsed event data has empty Indexed - ' + index);
} else {
assert.ok(equals(parsed[index], expected), 'parsed event data matches - ' + index);
}
});
});
});
});

File diff suppressed because one or more lines are too long

Binary file not shown.