2017-02-24 23:10:28 +03:00
|
|
|
'use strict';
|
|
|
|
|
2017-03-01 10:33:29 +03:00
|
|
|
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-04-05 00:32:04 +03:00
|
|
|
var throwError = require('ethers-utils/throw-error');
|
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
var utils = (function() {
|
|
|
|
var convert = require('ethers-utils/convert.js');
|
|
|
|
var utf8 = require('ethers-utils/utf8.js');
|
|
|
|
|
|
|
|
return {
|
|
|
|
defineProperty: require('ethers-utils/properties.js').defineProperty,
|
|
|
|
|
|
|
|
arrayify: convert.arrayify,
|
|
|
|
padZeros: convert.padZeros,
|
|
|
|
|
|
|
|
bigNumberify: require('ethers-utils/bignumber.js').bigNumberify,
|
|
|
|
|
2017-03-01 10:33:29 +03:00
|
|
|
getAddress: require('ethers-utils/address').getAddress,
|
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
concat: convert.concat,
|
|
|
|
|
|
|
|
toUtf8Bytes: utf8.toUtf8Bytes,
|
|
|
|
toUtf8String: utf8.toUtf8String,
|
|
|
|
|
|
|
|
hexlify: convert.hexlify,
|
|
|
|
isHexString: convert.isHexString,
|
|
|
|
|
|
|
|
keccak256: require('ethers-utils/keccak256.js'),
|
|
|
|
};
|
|
|
|
})();
|
|
|
|
|
|
|
|
// Creates property that is immutable
|
|
|
|
function defineFrozen(object, name, value) {
|
|
|
|
var frozen = JSON.stringify(value);
|
|
|
|
Object.defineProperty(object, name, {
|
|
|
|
enumerable: true,
|
|
|
|
get: function() { return JSON.parse(frozen); }
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-11-22 03:24:44 +03:00
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
// getKeys([{a: 1, b: 2}, {a: 3, b: 4}], 'a') => [1, 3]
|
|
|
|
function getKeys(params, key, allowEmpty) {
|
2017-04-05 00:32:04 +03:00
|
|
|
if (!Array.isArray(params)) { throwError('invalid params', {params: params}); }
|
2017-02-24 23:10:28 +03:00
|
|
|
|
|
|
|
var result = [];
|
|
|
|
|
|
|
|
for (var i = 0; i < params.length; i++) {
|
|
|
|
var value = params[i][key];
|
|
|
|
if (allowEmpty && !value) {
|
|
|
|
value = '';
|
|
|
|
} else if (typeof(value) !== 'string') {
|
2017-04-05 00:32:04 +03:00
|
|
|
throwError('invalid abi', {params: params, key: key, value: value});
|
2017-02-24 23:10:28 +03:00
|
|
|
}
|
|
|
|
result.push(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-11-22 03:24:44 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
var coderNull = {
|
|
|
|
name: 'null',
|
|
|
|
type: '',
|
|
|
|
encode: function(value) {
|
|
|
|
return utils.arrayify([]);
|
|
|
|
},
|
|
|
|
decode: function(data, offset) {
|
|
|
|
if (offset > data.length) { throw new Error('invalid null'); }
|
|
|
|
return {
|
|
|
|
consumed: 0,
|
|
|
|
value: undefined
|
|
|
|
}
|
|
|
|
},
|
|
|
|
dynamic: false
|
|
|
|
};
|
|
|
|
|
2017-11-22 03:24:44 +03:00
|
|
|
function coderNumber(size, signed, localName) {
|
2017-11-07 03:35:18 +03:00
|
|
|
var name = ((signed ? 'int': 'uint') + size);
|
2017-02-24 23:10:28 +03:00
|
|
|
return {
|
2017-11-22 03:24:44 +03:00
|
|
|
localName: localName,
|
2017-11-07 03:35:18 +03:00
|
|
|
name: name,
|
|
|
|
type: name,
|
2017-02-24 23:10:28 +03:00
|
|
|
encode: function(value) {
|
|
|
|
value = utils.bigNumberify(value).toTwos(size * 8).maskn(size * 8);
|
|
|
|
//value = value.toTwos(size * 8).maskn(size * 8);
|
|
|
|
if (signed) {
|
|
|
|
value = value.fromTwos(size * 8).toTwos(256);
|
|
|
|
}
|
|
|
|
return utils.padZeros(utils.arrayify(value), 32);
|
|
|
|
},
|
|
|
|
decode: function(data, offset) {
|
|
|
|
var junkLength = 32 - size;
|
|
|
|
var value = utils.bigNumberify(data.slice(offset + junkLength, offset + 32));
|
|
|
|
if (signed) {
|
|
|
|
value = value.fromTwos(size * 8);
|
|
|
|
} else {
|
|
|
|
value = value.maskn(size * 8);
|
|
|
|
}
|
2017-04-05 00:32:04 +03:00
|
|
|
|
|
|
|
if (size <= 6) { value = value.toNumber(); }
|
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
return {
|
|
|
|
consumed: 32,
|
|
|
|
value: value,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
var uint256Coder = coderNumber(32, false);
|
|
|
|
|
2017-11-22 03:24:44 +03:00
|
|
|
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()
|
|
|
|
}
|
2017-02-24 23:10:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-22 03:24:44 +03:00
|
|
|
function coderFixedBytes(length, localName) {
|
2017-11-07 03:35:18 +03:00
|
|
|
var name = ('bytes' + length);
|
2017-02-24 23:10:28 +03:00
|
|
|
return {
|
2017-11-22 03:24:44 +03:00
|
|
|
localName: localName,
|
2017-11-07 03:35:18 +03:00
|
|
|
name: name,
|
|
|
|
type: name,
|
2017-02-24 23:10:28 +03:00
|
|
|
encode: function(value) {
|
|
|
|
value = utils.arrayify(value);
|
|
|
|
if (length === 32) { return value; }
|
|
|
|
|
|
|
|
var result = new Uint8Array(32);
|
|
|
|
result.set(value);
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
decode: function(data, offset) {
|
2017-04-05 00:32:04 +03:00
|
|
|
if (data.length < offset + 32) { throwError('invalid bytes' + length); }
|
2017-02-24 23:10:28 +03:00
|
|
|
|
|
|
|
return {
|
|
|
|
consumed: 32,
|
2017-04-05 00:32:04 +03:00
|
|
|
value: utils.hexlify(data.slice(offset, offset + length))
|
2017-02-24 23:10:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-11-22 03:24:44 +03:00
|
|
|
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)))
|
|
|
|
}
|
2017-02-24 23:10:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function _encodeDynamicBytes(value) {
|
|
|
|
var dataLength = parseInt(32 * Math.ceil(value.length / 32));
|
|
|
|
var padding = new Uint8Array(dataLength - value.length);
|
|
|
|
|
|
|
|
return utils.concat([
|
|
|
|
uint256Coder.encode(value.length),
|
|
|
|
value,
|
|
|
|
padding
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
function _decodeDynamicBytes(data, offset) {
|
2017-04-05 00:32:04 +03:00
|
|
|
if (data.length < offset + 32) { throwError('invalid bytes'); }
|
2017-02-24 23:10:28 +03:00
|
|
|
|
|
|
|
var length = uint256Coder.decode(data, offset).value;
|
|
|
|
length = length.toNumber();
|
2017-04-05 00:32:04 +03:00
|
|
|
if (data.length < offset + 32 + length) { throwError('invalid bytes'); }
|
2017-02-24 23:10:28 +03:00
|
|
|
|
|
|
|
return {
|
|
|
|
consumed: parseInt(32 + 32 * Math.ceil(length / 32)),
|
|
|
|
value: data.slice(offset + 32, offset + 32 + length),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-22 03:24:44 +03:00
|
|
|
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
|
|
|
|
};
|
|
|
|
}
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-11-22 03:24:44 +03:00
|
|
|
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
|
|
|
|
};
|
|
|
|
}
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
function alignSize(size) {
|
|
|
|
return parseInt(32 * Math.ceil(size / 32));
|
|
|
|
}
|
|
|
|
|
|
|
|
function pack(coders, values) {
|
2018-01-11 03:20:18 +03:00
|
|
|
if (Array.isArray(values)) {
|
|
|
|
if (coders.length !== values.length) {
|
|
|
|
throwError('types/values mismatch', { type: type, values: values });
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (values && typeof(values) === 'object') {
|
|
|
|
var arrayValues = [];
|
|
|
|
coders.forEach(function(coder) {
|
|
|
|
arrayValues.push(values[coder.localName]);
|
|
|
|
});
|
|
|
|
values = arrayValues;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
throwError('invalid value', { type: 'tuple', values: values });
|
|
|
|
}
|
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
var parts = [];
|
|
|
|
|
|
|
|
coders.forEach(function(coder, index) {
|
|
|
|
parts.push({ dynamic: coder.dynamic, value: coder.encode(values[index]) });
|
2018-01-11 03:20:18 +03:00
|
|
|
});
|
2017-11-07 03:35:18 +03:00
|
|
|
|
|
|
|
var staticSize = 0, dynamicSize = 0;
|
|
|
|
parts.forEach(function(part, index) {
|
|
|
|
if (part.dynamic) {
|
|
|
|
staticSize += 32;
|
|
|
|
dynamicSize += alignSize(part.value.length);
|
|
|
|
} else {
|
|
|
|
staticSize += alignSize(part.value.length);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var offset = 0, dynamicOffset = staticSize;
|
|
|
|
var data = new Uint8Array(staticSize + dynamicSize);
|
|
|
|
|
|
|
|
parts.forEach(function(part, index) {
|
|
|
|
if (part.dynamic) {
|
|
|
|
//uint256Coder.encode(dynamicOffset).copy(data, offset);
|
|
|
|
data.set(uint256Coder.encode(dynamicOffset), offset);
|
|
|
|
offset += 32;
|
|
|
|
|
|
|
|
//part.value.copy(data, dynamicOffset); @TODO
|
|
|
|
data.set(part.value, dynamicOffset);
|
|
|
|
dynamicOffset += alignSize(part.value.length);
|
|
|
|
} else {
|
|
|
|
//part.value.copy(data, offset); @TODO
|
|
|
|
data.set(part.value, offset);
|
|
|
|
offset += alignSize(part.value.length);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
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);
|
|
|
|
var result = coder.decode(data, baseOffset + dynamicOffset.value.toNumber());
|
|
|
|
// The dynamic part is leap-frogged somewhere else; doesn't count towards size
|
|
|
|
result.consumed = dynamicOffset.consumed;
|
|
|
|
} else {
|
|
|
|
var result = coder.decode(data, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (result.value != undefined) {
|
|
|
|
value.push(result.value);
|
|
|
|
}
|
|
|
|
|
|
|
|
offset += result.consumed;
|
|
|
|
consumed += result.consumed;
|
|
|
|
});
|
|
|
|
|
2017-11-22 03:24:44 +03:00
|
|
|
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];
|
|
|
|
});
|
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
return {
|
|
|
|
value: value,
|
|
|
|
consumed: consumed
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-11-22 03:24:44 +03:00
|
|
|
function coderArray(coder, length, localName) {
|
2017-11-07 03:35:18 +03:00
|
|
|
var type = (coder.type + '[' + (length >= 0 ? length: '') + ']');
|
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
return {
|
2017-11-07 03:35:18 +03:00
|
|
|
coder: coder,
|
2017-11-22 03:24:44 +03:00
|
|
|
localName: localName,
|
2017-11-07 03:35:18 +03:00
|
|
|
length: length,
|
|
|
|
name: 'array',
|
|
|
|
type: type,
|
2017-02-24 23:10:28 +03:00
|
|
|
encode: function(value) {
|
2017-04-05 00:32:04 +03:00
|
|
|
if (!Array.isArray(value)) { throwError('invalid array'); }
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
var count = length;
|
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
var result = new Uint8Array(0);
|
2017-11-07 03:35:18 +03:00
|
|
|
if (count === -1) {
|
|
|
|
count = value.length;
|
|
|
|
result = uint256Coder.encode(count);
|
2017-02-24 23:10:28 +03:00
|
|
|
}
|
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
if (count !== value.length) { throwError('size mismatch'); }
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
var coders = [];
|
|
|
|
value.forEach(function(value) { coders.push(coder); });
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
return utils.concat([result, pack(coders, value)]);
|
2017-02-24 23:10:28 +03:00
|
|
|
},
|
|
|
|
decode: function(data, offset) {
|
|
|
|
// @TODO:
|
|
|
|
//if (data.length < offset + length * 32) { throw new Error('invalid array'); }
|
|
|
|
|
|
|
|
var consumed = 0;
|
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
var count = length;
|
|
|
|
|
|
|
|
if (count === -1) {
|
|
|
|
var decodedLength = uint256Coder.decode(data, offset);
|
|
|
|
count = decodedLength.value.toNumber();
|
|
|
|
consumed += decodedLength.consumed;
|
|
|
|
offset += decodedLength.consumed;
|
2017-02-24 23:10:28 +03:00
|
|
|
}
|
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
var coders = [];
|
|
|
|
for (var i = 0; i < count; i++) { coders.push(coder); }
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
var result = unpack(coders, data, offset);
|
|
|
|
result.consumed += consumed;
|
|
|
|
return result;
|
|
|
|
},
|
|
|
|
dynamic: (length === -1 || coder.dynamic)
|
|
|
|
}
|
|
|
|
}
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
|
2017-11-22 03:24:44 +03:00
|
|
|
function coderTuple(coders, localName) {
|
2017-11-07 03:35:18 +03:00
|
|
|
var dynamic = false;
|
|
|
|
var types = [];
|
|
|
|
coders.forEach(function(coder) {
|
|
|
|
if (coder.dynamic) { dynamic = true; }
|
|
|
|
types.push(coder.type);
|
|
|
|
});
|
|
|
|
|
|
|
|
var type = ('tuple(' + types.join(',') + ')');
|
|
|
|
|
|
|
|
return {
|
|
|
|
coders: coders,
|
2017-11-22 03:24:44 +03:00
|
|
|
localName: localName,
|
2017-11-07 03:35:18 +03:00
|
|
|
name: 'tuple',
|
|
|
|
type: type,
|
|
|
|
encode: function(value) {
|
|
|
|
return pack(coders, value);
|
2017-02-24 23:10:28 +03:00
|
|
|
},
|
2017-11-07 03:35:18 +03:00
|
|
|
decode: function(data, offset) {
|
|
|
|
return unpack(coders, data, offset);
|
|
|
|
},
|
|
|
|
dynamic: dynamic
|
|
|
|
};
|
2017-02-24 23:10:28 +03:00
|
|
|
}
|
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
function getTypes(coders) {
|
|
|
|
var type = coderTuple(coders).type;
|
|
|
|
return type.substring(6, type.length - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
function splitNesting(value) {
|
|
|
|
var result = [];
|
|
|
|
var accum = '';
|
|
|
|
var depth = 0;
|
|
|
|
for (var offset = 0; offset < value.length; offset++) {
|
|
|
|
var c = value[offset];
|
|
|
|
if (c === ',' && depth === 0) {
|
|
|
|
result.push(accum);
|
|
|
|
accum = '';
|
|
|
|
} else {
|
|
|
|
accum += c;
|
|
|
|
if (c === '(') {
|
|
|
|
depth++;
|
|
|
|
} else if (c === ')') {
|
|
|
|
depth--;
|
|
|
|
if (depth === -1) {
|
|
|
|
throw new Error('unbalanced parenthsis');
|
2017-02-24 23:10:28 +03:00
|
|
|
}
|
2017-11-07 03:35:18 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result.push(accum);
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
return result;
|
|
|
|
}
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
var paramTypeBytes = new RegExp(/^bytes([0-9]*)$/);
|
|
|
|
var paramTypeNumber = new RegExp(/^(u?int)([0-9]*)$/);
|
|
|
|
var paramTypeArray = new RegExp(/^(.*)\[([0-9]*)\]$/);
|
|
|
|
var paramTypeSimple = {
|
|
|
|
address: coderAddress,
|
|
|
|
bool: coderBoolean,
|
|
|
|
string: coderString,
|
|
|
|
bytes: coderDynamicBytes,
|
|
|
|
};
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-11-22 03:24:44 +03:00
|
|
|
function getParamCoder(type, localName) {
|
2017-11-07 03:35:18 +03:00
|
|
|
var coder = paramTypeSimple[type];
|
2017-11-22 03:24:44 +03:00
|
|
|
if (coder) { return coder(localName); }
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
var match = type.match(paramTypeNumber);
|
|
|
|
if (match) {
|
|
|
|
var size = parseInt(match[2] || 256);
|
|
|
|
if (size === 0 || size > 256 || (size % 8) !== 0) {
|
|
|
|
throwError('invalid type', { type: type });
|
|
|
|
}
|
2017-11-22 03:24:44 +03:00
|
|
|
return coderNumber(size / 8, (match[1] === 'int'), localName);
|
2017-11-07 03:35:18 +03:00
|
|
|
}
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
var match = type.match(paramTypeBytes);
|
|
|
|
if (match) {
|
|
|
|
var size = parseInt(match[1]);
|
|
|
|
if (size === 0 || size > 32) {
|
|
|
|
throwError('invalid type ' + type);
|
2017-02-24 23:10:28 +03:00
|
|
|
}
|
2017-11-22 03:24:44 +03:00
|
|
|
return coderFixedBytes(size, localName);
|
2017-11-07 03:35:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
var match = type.match(paramTypeArray);
|
|
|
|
if (match) {
|
|
|
|
var size = parseInt(match[2] || -1);
|
2017-11-22 03:24:44 +03:00
|
|
|
return coderArray(getParamCoder(match[1], localName), size, localName);
|
2017-11-07 03:35:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (type.substring(0, 6) === 'tuple(' && type.substring(type.length - 1) === ')') {
|
|
|
|
var coders = [];
|
2017-11-22 03:24:44 +03:00
|
|
|
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]));
|
2017-11-07 03:35:18 +03:00
|
|
|
});
|
2017-11-22 03:24:44 +03:00
|
|
|
return coderTuple(coders, localName);
|
2017-11-07 03:35:18 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (type === '') {
|
|
|
|
return coderNull;
|
2017-02-24 23:10:28 +03:00
|
|
|
}
|
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
throwError('invalid type', { type: type });
|
2017-02-24 23:10:28 +03:00
|
|
|
}
|
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
function populateDescription(object, items) {
|
|
|
|
for (var key in items) {
|
|
|
|
utils.defineProperty(object, key, items[key]);
|
|
|
|
}
|
|
|
|
return object;
|
|
|
|
}
|
|
|
|
|
|
|
|
function CallDescription() { }
|
|
|
|
utils.defineProperty(CallDescription.prototype, 'type', 'call');
|
|
|
|
|
2017-03-01 10:33:29 +03:00
|
|
|
function DeployDescription() { }
|
|
|
|
utils.defineProperty(DeployDescription.prototype, 'type', 'deploy');
|
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
function TransactionDescription() { }
|
|
|
|
utils.defineProperty(TransactionDescription.prototype, 'type', 'transaction');
|
|
|
|
|
|
|
|
function EventDescription() { }
|
|
|
|
utils.defineProperty(EventDescription.prototype, 'type', 'event');
|
|
|
|
|
2017-11-22 03:24:44 +03:00
|
|
|
function Indexed(value) {
|
|
|
|
utils.defineProperty(this, 'indexed', true);
|
|
|
|
utils.defineProperty(this, 'hash', value);
|
|
|
|
}
|
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
function Interface(abi) {
|
|
|
|
if (!(this instanceof Interface)) { throw new Error('missing new'); }
|
|
|
|
|
2017-03-01 10:33:29 +03:00
|
|
|
if (typeof(abi) === 'string') {
|
|
|
|
try {
|
|
|
|
abi = JSON.parse(abi);
|
|
|
|
} catch (error) {
|
2017-04-05 00:32:04 +03:00
|
|
|
throwError('invalid abi', { input: abi });
|
2017-03-01 10:33:29 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
// Wrap this up as JSON so we can return a "copy" and avoid mutation
|
|
|
|
defineFrozen(this, 'abi', abi);
|
|
|
|
|
2017-03-08 09:52:23 +03:00
|
|
|
var methods = {}, events = {}, deploy = null;
|
2017-04-05 00:32:04 +03:00
|
|
|
|
|
|
|
utils.defineProperty(this, 'functions', methods);
|
|
|
|
utils.defineProperty(this, 'events', events);
|
|
|
|
|
2017-03-01 10:33:29 +03:00
|
|
|
function addMethod(method) {
|
2017-02-24 23:10:28 +03:00
|
|
|
|
|
|
|
switch (method.type) {
|
2017-03-01 10:33:29 +03:00
|
|
|
case 'constructor':
|
|
|
|
var func = (function() {
|
2018-01-11 03:20:18 +03:00
|
|
|
var inputParams = parseParams(method.inputs);
|
2017-03-01 10:33:29 +03:00
|
|
|
var func = function(bytecode) {
|
|
|
|
if (!utils.isHexString(bytecode)) {
|
2018-01-11 03:20:18 +03:00
|
|
|
throwError('invalid bytecode', { input: bytecode });
|
2017-03-01 10:33:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
var params = Array.prototype.slice.call(arguments, 1);
|
2018-01-11 03:20:18 +03:00
|
|
|
if (params.length < inputParams.types.length) {
|
2017-04-05 00:32:04 +03:00
|
|
|
throwError('missing parameter');
|
2018-01-11 03:20:18 +03:00
|
|
|
} else if (params.length > inputParams.types.length) {
|
2017-04-05 00:32:04 +03:00
|
|
|
throwError('too many parameters');
|
2017-03-01 10:33:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
var result = {
|
2018-01-11 03:20:18 +03:00
|
|
|
bytecode: bytecode + Interface.encodeParams(inputParams.names, inputParams.types, params).substring(2),
|
2017-03-01 10:33:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return populateDescription(new DeployDescription(), result);
|
|
|
|
}
|
|
|
|
|
2018-01-11 03:20:18 +03:00
|
|
|
defineFrozen(func, 'inputs', inputParams);
|
2017-03-01 10:33:29 +03:00
|
|
|
|
|
|
|
return func;
|
|
|
|
})();
|
|
|
|
|
2017-03-08 09:52:23 +03:00
|
|
|
if (!deploy) { deploy = func; }
|
2017-03-01 10:33:29 +03:00
|
|
|
|
|
|
|
break;
|
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
case 'function':
|
2017-03-01 10:33:29 +03:00
|
|
|
var func = (function() {
|
2017-11-22 03:24:44 +03:00
|
|
|
var inputParams = parseParams(method.inputs);
|
|
|
|
var outputParams = parseParams(method.outputs);
|
|
|
|
|
2017-03-01 10:33:29 +03:00
|
|
|
if (method.constant) {
|
2017-11-22 03:24:44 +03:00
|
|
|
var outputTypes = outputParams.types;
|
|
|
|
var outputNames = outputParams.names;
|
2017-03-01 10:33:29 +03:00
|
|
|
}
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2018-01-10 04:45:38 +03:00
|
|
|
var signature = '(' + inputParams.types.join(',') + ')';
|
|
|
|
signature = signature.replace(/tuple/g, '');
|
|
|
|
signature = method.name + signature;
|
|
|
|
|
2017-10-27 07:24:59 +03:00
|
|
|
var sighash = utils.keccak256(utils.toUtf8Bytes(signature)).substring(0, 10);
|
2017-02-24 23:10:28 +03:00
|
|
|
var func = function() {
|
|
|
|
var result = {
|
|
|
|
name: method.name,
|
|
|
|
signature: signature,
|
2017-10-27 07:24:59 +03:00
|
|
|
sighash: sighash
|
2017-02-24 23:10:28 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
var params = Array.prototype.slice.call(arguments, 0);
|
|
|
|
|
2018-01-11 03:20:18 +03:00
|
|
|
if (params.length < inputParams.types.length) {
|
2017-04-05 00:32:04 +03:00
|
|
|
throwError('missing parameter');
|
2018-01-11 03:20:18 +03:00
|
|
|
} else if (params.length > inputParams.types.length) {
|
2017-04-05 00:32:04 +03:00
|
|
|
throwError('too many parameters');
|
2017-02-24 23:10:28 +03:00
|
|
|
}
|
|
|
|
|
2018-01-11 03:20:18 +03:00
|
|
|
result.data = sighash + Interface.encodeParams(inputParams.names, inputParams.types, params).substring(2);
|
2017-02-24 23:10:28 +03:00
|
|
|
if (method.constant) {
|
|
|
|
result.parse = function(data) {
|
|
|
|
return Interface.decodeParams(
|
|
|
|
outputNames,
|
|
|
|
outputTypes,
|
|
|
|
utils.arrayify(data)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
return populateDescription(new CallDescription(), result);
|
|
|
|
}
|
|
|
|
|
|
|
|
return populateDescription(new TransactionDescription(), result);
|
|
|
|
}
|
|
|
|
|
2018-01-11 03:20:18 +03:00
|
|
|
defineFrozen(func, 'inputs', inputParams);
|
|
|
|
defineFrozen(func, 'outputs', outputParams);
|
2018-01-10 04:45:38 +03:00
|
|
|
|
2017-10-27 07:24:59 +03:00
|
|
|
utils.defineProperty(func, 'signature', signature);
|
|
|
|
utils.defineProperty(func, 'sighash', sighash);
|
2017-02-24 23:10:28 +03:00
|
|
|
|
|
|
|
return func;
|
|
|
|
})();
|
2017-03-01 10:33:29 +03:00
|
|
|
|
2017-04-05 00:32:04 +03:00
|
|
|
if (method.name && method.name !== 'deployFunction' && methods[method.name] == null) {
|
2017-03-01 10:33:29 +03:00
|
|
|
utils.defineProperty(methods, method.name, func);
|
|
|
|
//} else if (this.fallbackFunction == null) {
|
|
|
|
// utils.defineProperty(this, 'fallbackFunction', func);
|
|
|
|
}
|
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'event':
|
2017-03-01 10:33:29 +03:00
|
|
|
var func = (function() {
|
2017-11-22 03:24:44 +03:00
|
|
|
// @TODO: Move to parseParams
|
2017-02-24 23:10:28 +03:00
|
|
|
var inputTypes = getKeys(method.inputs, 'type');
|
2018-01-10 04:45:38 +03:00
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
var func = function() {
|
2017-11-22 03:24:44 +03:00
|
|
|
// @TODO: Move to parseParams
|
2017-02-24 23:10:28 +03:00
|
|
|
var signature = method.name + '(' + getKeys(method.inputs, 'type').join(',') + ')';
|
2018-01-10 04:45:38 +03:00
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
var result = {
|
|
|
|
inputs: method.inputs,
|
|
|
|
name: method.name,
|
|
|
|
signature: signature,
|
|
|
|
topics: [utils.keccak256(utils.toUtf8Bytes(signature))],
|
|
|
|
};
|
2017-05-10 04:48:56 +03:00
|
|
|
|
|
|
|
result.parse = function(topics, data) {
|
2017-11-22 03:24:44 +03:00
|
|
|
if (data == null) {
|
|
|
|
data = topics;
|
|
|
|
topics = null;
|
|
|
|
}
|
2017-05-10 04:48:56 +03:00
|
|
|
|
|
|
|
// Strip the signature off of non-anonymous topics
|
2017-11-22 03:24:44 +03:00
|
|
|
if (topics != null && !method.anonymous) { topics = topics.slice(1); }
|
2017-05-10 04:48:56 +03:00
|
|
|
|
|
|
|
var inputNamesIndexed = [], inputNamesNonIndexed = [];
|
|
|
|
var inputTypesIndexed = [], inputTypesNonIndexed = [];
|
2017-11-22 03:24:44 +03:00
|
|
|
var inputDynamic = [];
|
2017-05-10 04:48:56 +03:00
|
|
|
method.inputs.forEach(function(input) {
|
|
|
|
if (input.indexed) {
|
2017-11-22 03:24:44 +03:00
|
|
|
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);
|
|
|
|
}
|
2017-05-10 04:48:56 +03:00
|
|
|
inputNamesIndexed.push(input.name);
|
|
|
|
} else {
|
|
|
|
inputNamesNonIndexed.push(input.name);
|
|
|
|
inputTypesNonIndexed.push(input.type);
|
2017-11-22 03:24:44 +03:00
|
|
|
inputDynamic.push(false);
|
2017-05-10 04:48:56 +03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-11-22 03:24:44 +03:00
|
|
|
if (topics != null) {
|
|
|
|
var resultIndexed = Interface.decodeParams(
|
|
|
|
inputNamesIndexed,
|
|
|
|
inputTypesIndexed,
|
|
|
|
utils.concat(topics)
|
|
|
|
);
|
|
|
|
}
|
2017-05-10 04:48:56 +03:00
|
|
|
|
|
|
|
var resultNonIndexed = Interface.decodeParams(
|
|
|
|
inputNamesNonIndexed,
|
|
|
|
inputTypesNonIndexed,
|
2017-02-24 23:10:28 +03:00
|
|
|
utils.arrayify(data)
|
|
|
|
);
|
2017-05-10 04:48:56 +03:00
|
|
|
|
|
|
|
var result = new Result();
|
|
|
|
var nonIndexedIndex = 0, indexedIndex = 0;
|
|
|
|
method.inputs.forEach(function(input, i) {
|
|
|
|
if (input.indexed) {
|
2017-11-22 03:24:44 +03:00
|
|
|
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++];
|
|
|
|
}
|
2017-05-10 04:48:56 +03:00
|
|
|
} else {
|
|
|
|
result[i] = resultNonIndexed[nonIndexedIndex++];
|
|
|
|
}
|
|
|
|
if (input.name) { result[input.name] = result[i]; }
|
|
|
|
});
|
|
|
|
|
|
|
|
result.length = method.inputs.length;
|
|
|
|
|
|
|
|
return result;
|
2017-02-24 23:10:28 +03:00
|
|
|
};
|
2018-01-10 04:45:38 +03:00
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
return populateDescription(new EventDescription(), result);
|
|
|
|
}
|
2017-10-27 07:24:59 +03:00
|
|
|
|
2018-01-10 04:45:38 +03:00
|
|
|
// Next Major Version: All the event parameters are known and should
|
|
|
|
// not require a function to be called to get them. We expose them
|
|
|
|
// here now, and in the future will remove the callable version and
|
|
|
|
// replace it with the EventDescription object
|
|
|
|
|
|
|
|
var info = func();
|
|
|
|
|
2017-11-22 03:24:44 +03:00
|
|
|
// @TODO: Move to parseParams
|
2017-02-24 23:10:28 +03:00
|
|
|
defineFrozen(func, 'inputs', getKeys(method.inputs, 'name'));
|
2017-10-27 07:24:59 +03:00
|
|
|
|
2018-01-10 04:45:38 +03:00
|
|
|
utils.defineProperty(func, 'parse', info.parse);
|
|
|
|
utils.defineProperty(func, 'signature', info.signature);
|
|
|
|
utils.defineProperty(func, 'topic', info.topics[0]);
|
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
return func;
|
|
|
|
})();
|
2017-03-01 10:33:29 +03:00
|
|
|
|
2018-01-10 04:45:38 +03:00
|
|
|
|
|
|
|
|
2017-03-01 10:33:29 +03:00
|
|
|
if (method.name && events[method.name] == null) {
|
|
|
|
utils.defineProperty(events, method.name, func);
|
|
|
|
}
|
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
break;
|
|
|
|
|
2017-04-05 00:32:04 +03:00
|
|
|
case 'fallback':
|
|
|
|
// Nothing to do for fallback
|
|
|
|
break;
|
|
|
|
|
2017-02-24 23:10:28 +03:00
|
|
|
default:
|
2017-03-01 10:33:29 +03:00
|
|
|
console.log('WARNING: unsupported ABI type - ' + method.type);
|
2017-02-24 23:10:28 +03:00
|
|
|
break;
|
|
|
|
}
|
2017-03-01 10:33:29 +03:00
|
|
|
};
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-05-10 04:48:56 +03:00
|
|
|
this.abi.forEach(addMethod, this);
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-03-01 10:33:29 +03:00
|
|
|
// If there wasn't a constructor, create the default constructor
|
2017-03-08 09:52:23 +03:00
|
|
|
if (!deploy) {
|
2017-03-01 10:33:29 +03:00
|
|
|
addMethod({type: 'constructor', inputs: []});
|
|
|
|
}
|
|
|
|
|
2017-03-08 09:52:23 +03:00
|
|
|
utils.defineProperty(this, 'deployFunction', deploy);
|
2017-02-24 23:10:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-01-11 03:20:18 +03:00
|
|
|
utils.defineProperty(Interface, 'encodeParams', function(names, types, values) {
|
|
|
|
|
|
|
|
// Names is optional, so shift over all the parameters if not provided
|
|
|
|
if (arguments.length < 3) {
|
|
|
|
values = types;
|
|
|
|
types = names;
|
|
|
|
names = null;
|
|
|
|
}
|
|
|
|
|
2017-04-05 00:32:04 +03:00
|
|
|
if (types.length !== values.length) { throwError('types/values mismatch', {types: types, values: values}); }
|
2017-02-24 23:10:28 +03:00
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
var coders = [];
|
2018-01-11 03:20:18 +03:00
|
|
|
types.forEach(function(type, index) {
|
|
|
|
coders.push(getParamCoder(type, (names ? names[index]: undefined)));
|
2017-02-24 23:10:28 +03:00
|
|
|
});
|
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
return utils.hexlify(coderTuple(coders).encode(values));
|
2017-02-24 23:10:28 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
function Result() {}
|
|
|
|
|
|
|
|
utils.defineProperty(Interface, 'decodeParams', function(names, types, data) {
|
|
|
|
|
|
|
|
// Names is optional, so shift over all the parameters if not provided
|
|
|
|
if (arguments.length < 3) {
|
|
|
|
data = types;
|
|
|
|
types = names;
|
2017-11-22 03:24:44 +03:00
|
|
|
names = null;
|
2017-02-24 23:10:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
data = utils.arrayify(data);
|
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
var coders = [];
|
2017-11-22 03:24:44 +03:00
|
|
|
types.forEach(function(type, index) {
|
|
|
|
coders.push(getParamCoder(type, (names ? names[index]: undefined)));
|
2017-11-07 03:35:18 +03:00
|
|
|
});
|
2017-03-01 10:33:29 +03:00
|
|
|
|
2017-11-07 03:35:18 +03:00
|
|
|
var values = new Result();
|
2017-11-22 03:24:44 +03:00
|
|
|
return coderTuple(coders).decode(data, 0).value;
|
2017-02-24 23:10:28 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
module.exports = Interface;
|