Make safe integers into JavaScript numbers. Added more detail error messages. Prevent contracts from hijacking "functions" and "events".

This commit is contained in:
ricmoo 2017-04-04 17:32:04 -04:00
parent 73eef741b6
commit d686374e05

View File

@ -2,6 +2,8 @@
// See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI // See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
var throwError = require('ethers-utils/throw-error');
var utils = (function() { var utils = (function() {
var convert = require('ethers-utils/convert.js'); var convert = require('ethers-utils/convert.js');
var utf8 = require('ethers-utils/utf8.js'); var utf8 = require('ethers-utils/utf8.js');
@ -39,7 +41,7 @@ function defineFrozen(object, name, value) {
// getKeys([{a: 1, b: 2}, {a: 3, b: 4}], 'a') => [1, 3] // getKeys([{a: 1, b: 2}, {a: 3, b: 4}], 'a') => [1, 3]
function getKeys(params, key, allowEmpty) { function getKeys(params, key, allowEmpty) {
if (!Array.isArray(params)) { throw new Error('invalid params'); } if (!Array.isArray(params)) { throwError('invalid params', {params: params}); }
var result = []; var result = [];
@ -48,7 +50,7 @@ function getKeys(params, key, allowEmpty) {
if (allowEmpty && !value) { if (allowEmpty && !value) {
value = ''; value = '';
} else if (typeof(value) !== 'string') { } else if (typeof(value) !== 'string') {
throw new Error('invalid abi'); throwError('invalid abi', {params: params, key: key, value: value});
} }
result.push(value); result.push(value);
} }
@ -74,6 +76,9 @@ function coderNumber(size, signed) {
} else { } else {
value = value.maskn(size * 8); value = value.maskn(size * 8);
} }
if (size <= 6) { value = value.toNumber(); }
return { return {
consumed: 32, consumed: 32,
value: value, value: value,
@ -107,11 +112,11 @@ function coderFixedBytes(length) {
return result; return result;
}, },
decode: function(data, offset) { decode: function(data, offset) {
if (data.length < offset + 32) { throw new Error('invalid bytes' + length); } if (data.length < offset + 32) { throwError('invalid bytes' + length); }
return { return {
consumed: 32, consumed: 32,
value: data.slice(offset, offset + length) value: utils.hexlify(data.slice(offset, offset + length))
} }
} }
}; };
@ -119,14 +124,13 @@ function coderFixedBytes(length) {
var coderAddress = { var coderAddress = {
encode: function(value) { encode: function(value) {
if (!utils.isHexString(value) && value.length === 42) { throw new Error('invalid address'); } value = utils.arrayify(utils.getAddress(value));
value = utils.arrayify(value);
var result = new Uint8Array(32); var result = new Uint8Array(32);
result.set(value, 12); result.set(value, 12);
return result; return result;
}, },
decode: function(data, offset) { decode: function(data, offset) {
if (data.length < offset + 32) { throw new Error('invalid address'); } if (data.length < offset + 32) { throwError('invalid address'); }
return { return {
consumed: 32, consumed: 32,
value: utils.getAddress(utils.hexlify(data.slice(offset + 12, offset + 32))) value: utils.getAddress(utils.hexlify(data.slice(offset + 12, offset + 32)))
@ -146,11 +150,11 @@ function _encodeDynamicBytes(value) {
} }
function _decodeDynamicBytes(data, offset) { function _decodeDynamicBytes(data, offset) {
if (data.length < offset + 32) { throw new Error('invalid bytes'); } if (data.length < offset + 32) { throwError('invalid bytes'); }
var length = uint256Coder.decode(data, offset).value; var length = uint256Coder.decode(data, offset).value;
length = length.toNumber(); length = length.toNumber();
if (data.length < offset + 32 + length) { throw new Error('invalid bytes'); } if (data.length < offset + 32 + length) { throwError('invalid bytes'); }
return { return {
consumed: parseInt(32 + 32 * Math.ceil(length / 32)), consumed: parseInt(32 + 32 * Math.ceil(length / 32)),
@ -164,7 +168,7 @@ var coderDynamicBytes = {
}, },
decode: function(data, offset) { decode: function(data, offset) {
var result = _decodeDynamicBytes(data, offset); var result = _decodeDynamicBytes(data, offset);
result.value = result.value; result.value = utils.hexlify(result.value);
return result; return result;
}, },
dynamic: true dynamic: true
@ -185,7 +189,7 @@ var coderString = {
function coderArray(coder, length) { function coderArray(coder, length) {
return { return {
encode: function(value) { encode: function(value) {
if (!Array.isArray(value)) { throw new Error('invalid array'); } if (!Array.isArray(value)) { throwError('invalid array'); }
var result = new Uint8Array(0); var result = new Uint8Array(0);
if (length === -1) { if (length === -1) {
@ -193,7 +197,7 @@ function coderArray(coder, length) {
result = uint256Coder.encode(length); result = uint256Coder.encode(length);
} }
if (length !== value.length) { throw new Error('size mismatch'); } if (length !== value.length) { throwError('size mismatch'); }
value.forEach(function(value) { value.forEach(function(value) {
result = utils.concat([result, coder.encode(value)]); result = utils.concat([result, coder.encode(value)]);
@ -240,36 +244,36 @@ function getParamCoder(type) {
var coder = null; var coder = null;
while (type) { while (type) {
var part = type.match(paramTypePart); var part = type.match(paramTypePart);
if (!part) { throw new Error('invalid type: ' + type); } if (!part) { throwError('invalid type', { type: type }); }
type = type.substring(part[0].length); type = type.substring(part[0].length);
var prefix = (part[2] || part[4] || part[5]); var prefix = (part[2] || part[4] || part[5]);
switch (prefix) { switch (prefix) {
case 'int': case 'uint': case 'int': case 'uint':
if (coder) { throw new Error('invalid type ' + type); } if (coder) { throwError('invalid type', { type: type }); }
var size = parseInt(part[3] || 256); var size = parseInt(part[3] || 256);
if (size === 0 || size > 256 || (size % 8) !== 0) { if (size === 0 || size > 256 || (size % 8) !== 0) {
throw new Error('invalid type ' + type); throwError('invalid type', { type: type });
} }
coder = coderNumber(size / 8, (prefix === 'int')); coder = coderNumber(size / 8, (prefix === 'int'));
break; break;
case 'bool': case 'bool':
if (coder) { throw new Error('invalid type ' + type); } if (coder) { throwError('invalid type', { type: type }); }
coder = coderBoolean; coder = coderBoolean;
break; break;
case 'string': case 'string':
if (coder) { throw new Error('invalid type ' + type); } if (coder) { throwError('invalid type', { type: type }); }
coder = coderString; coder = coderString;
break; break;
case 'bytes': case 'bytes':
if (coder) { throw new Error('invalid type ' + type); } if (coder) { throwError('invalid type', { type: type }); }
if (part[3]) { if (part[3]) {
var size = parseInt(part[3]); var size = parseInt(part[3]);
if (size === 0 || size > 32) { if (size === 0 || size > 32) {
throw new Error('invalid type ' + type); throwError('invalid type ' + type);
} }
coder = coderFixedBytes(size); coder = coderFixedBytes(size);
} else { } else {
@ -278,24 +282,24 @@ function getParamCoder(type) {
break; break;
case 'address': case 'address':
if (coder) { throw new Error('invalid type ' + type); } if (coder) { throwError('invalid type', { type: type }); }
coder = coderAddress; coder = coderAddress;
break; break;
case '[]': case '[]':
if (!coder || coder.dynamic) { throw new Error('invalid type ' + type); } if (!coder || coder.dynamic) { throwError('invalid type', { type: type }); }
coder = coderArray(coder, -1); coder = coderArray(coder, -1);
break; break;
// "[0-9+]" // "[0-9+]"
default: default:
if (!coder || coder.dynamic) { throw new Error('invalid type ' + type); } if (!coder || coder.dynamic) { throwError('invalid type', { type: type }); }
var size = parseInt(part[6]); var size = parseInt(part[6]);
coder = coderArray(coder, size); coder = coderArray(coder, size);
} }
} }
if (!coder) { throw new Error('invalid type'); } if (!coder) { throwError('invalid type'); }
return coder; return coder;
} }
@ -325,7 +329,7 @@ function Interface(abi) {
try { try {
abi = JSON.parse(abi); abi = JSON.parse(abi);
} catch (error) { } catch (error) {
throw new Error('invalid abi'); throwError('invalid abi', { input: abi });
} }
} }
@ -333,6 +337,10 @@ function Interface(abi) {
defineFrozen(this, 'abi', abi); defineFrozen(this, 'abi', abi);
var methods = {}, events = {}, deploy = null; var methods = {}, events = {}, deploy = null;
utils.defineProperty(this, 'functions', methods);
utils.defineProperty(this, 'events', events);
function addMethod(method) { function addMethod(method) {
switch (method.type) { switch (method.type) {
@ -341,14 +349,14 @@ function Interface(abi) {
var inputTypes = getKeys(method.inputs, 'type'); var inputTypes = getKeys(method.inputs, 'type');
var func = function(bytecode) { var func = function(bytecode) {
if (!utils.isHexString(bytecode)) { if (!utils.isHexString(bytecode)) {
throw new Error('invalid bytecode'); throwError('invalid bytecode', {input: bytecode});
} }
var params = Array.prototype.slice.call(arguments, 1); var params = Array.prototype.slice.call(arguments, 1);
if (params.length < inputTypes.length) { if (params.length < inputTypes.length) {
throw new Error('missing parameter'); throwError('missing parameter');
} else if (params.length > inputTypes.length) { } else if (params.length > inputTypes.length) {
throw new Error('too many parameters'); throwError('too many parameters');
} }
var result = { var result = {
@ -385,9 +393,9 @@ function Interface(abi) {
var params = Array.prototype.slice.call(arguments, 0); var params = Array.prototype.slice.call(arguments, 0);
if (params.length < inputTypes.length) { if (params.length < inputTypes.length) {
throw new Error('missing parameter'); throwError('missing parameter');
} else if (params.length > inputTypes.length) { } else if (params.length > inputTypes.length) {
throw new Error('too many parameters'); throwError('too many parameters');
} }
signature = utils.keccak256(utils.toUtf8Bytes(signature)).substring(0, 10); signature = utils.keccak256(utils.toUtf8Bytes(signature)).substring(0, 10);
@ -413,7 +421,7 @@ function Interface(abi) {
return func; return func;
})(); })();
if (method.name && methods[method.name] == null) { if (method.name && method.name !== 'deployFunction' && methods[method.name] == null) {
utils.defineProperty(methods, method.name, func); utils.defineProperty(methods, method.name, func);
//} else if (this.fallbackFunction == null) { //} else if (this.fallbackFunction == null) {
// utils.defineProperty(this, 'fallbackFunction', func); // utils.defineProperty(this, 'fallbackFunction', func);
@ -452,6 +460,10 @@ function Interface(abi) {
break; break;
case 'fallback':
// Nothing to do for fallback
break;
default: default:
console.log('WARNING: unsupported ABI type - ' + method.type); console.log('WARNING: unsupported ABI type - ' + method.type);
break; break;
@ -465,14 +477,12 @@ function Interface(abi) {
addMethod({type: 'constructor', inputs: []}); addMethod({type: 'constructor', inputs: []});
} }
utils.defineProperty(this, 'functions', methods);
utils.defineProperty(this, 'events', events);
utils.defineProperty(this, 'deployFunction', deploy); utils.defineProperty(this, 'deployFunction', deploy);
} }
utils.defineProperty(Interface, 'encodeParams', function(types, values) { utils.defineProperty(Interface, 'encodeParams', function(types, values) {
if (types.length !== values.length) { throw new Error('types/values mismatch'); } if (types.length !== values.length) { throwError('types/values mismatch', {types: types, values: values}); }
var parts = []; var parts = [];
@ -569,7 +579,7 @@ utils.defineProperty(Interface, 'decodeParams', function(names, types, data) {
return values; return values;
}); });
utils.defineProperty(Interface, 'getDeployTransaction', function(bytecode) { //utils.defineProperty(Interface, 'getDeployTransaction', function(bytecode) {
}); //});
module.exports = Interface; module.exports = Interface;