From d686374e05e10cab1c5d6475e8ab71c883144209 Mon Sep 17 00:00:00 2001 From: ricmoo Date: Tue, 4 Apr 2017 17:32:04 -0400 Subject: [PATCH] Make safe integers into JavaScript numbers. Added more detail error messages. Prevent contracts from hijacking "functions" and "events". --- contracts/interface.js | 80 ++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 35 deletions(-) diff --git a/contracts/interface.js b/contracts/interface.js index ae17c0ced..e909c9575 100644 --- a/contracts/interface.js +++ b/contracts/interface.js @@ -2,6 +2,8 @@ // See: https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI +var throwError = require('ethers-utils/throw-error'); + var utils = (function() { var convert = require('ethers-utils/convert.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] 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 = []; @@ -48,7 +50,7 @@ function getKeys(params, key, allowEmpty) { if (allowEmpty && !value) { value = ''; } else if (typeof(value) !== 'string') { - throw new Error('invalid abi'); + throwError('invalid abi', {params: params, key: key, value: value}); } result.push(value); } @@ -74,6 +76,9 @@ function coderNumber(size, signed) { } else { value = value.maskn(size * 8); } + + if (size <= 6) { value = value.toNumber(); } + return { consumed: 32, value: value, @@ -107,11 +112,11 @@ function coderFixedBytes(length) { return result; }, 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 { 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 = { encode: function(value) { - if (!utils.isHexString(value) && value.length === 42) { throw new Error('invalid address'); } - value = utils.arrayify(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) { throw new Error('invalid address'); } + if (data.length < offset + 32) { throwError('invalid address'); } return { consumed: 32, value: utils.getAddress(utils.hexlify(data.slice(offset + 12, offset + 32))) @@ -146,11 +150,11 @@ function _encodeDynamicBytes(value) { } 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; length = length.toNumber(); - if (data.length < offset + 32 + length) { throw new Error('invalid bytes'); } + if (data.length < offset + 32 + length) { throwError('invalid bytes'); } return { consumed: parseInt(32 + 32 * Math.ceil(length / 32)), @@ -164,7 +168,7 @@ var coderDynamicBytes = { }, decode: function(data, offset) { var result = _decodeDynamicBytes(data, offset); - result.value = result.value; + result.value = utils.hexlify(result.value); return result; }, dynamic: true @@ -185,7 +189,7 @@ var coderString = { function coderArray(coder, length) { return { encode: function(value) { - if (!Array.isArray(value)) { throw new Error('invalid array'); } + if (!Array.isArray(value)) { throwError('invalid array'); } var result = new Uint8Array(0); if (length === -1) { @@ -193,7 +197,7 @@ function coderArray(coder, 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) { result = utils.concat([result, coder.encode(value)]); @@ -240,36 +244,36 @@ function getParamCoder(type) { var coder = null; while (type) { 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); var prefix = (part[2] || part[4] || part[5]); switch (prefix) { 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); 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')); break; case 'bool': - if (coder) { throw new Error('invalid type ' + type); } + if (coder) { throwError('invalid type', { type: type }); } coder = coderBoolean; break; case 'string': - if (coder) { throw new Error('invalid type ' + type); } + if (coder) { throwError('invalid type', { type: type }); } coder = coderString; break; case 'bytes': - if (coder) { throw new Error('invalid type ' + type); } + if (coder) { throwError('invalid type', { type: type }); } if (part[3]) { var size = parseInt(part[3]); if (size === 0 || size > 32) { - throw new Error('invalid type ' + type); + throwError('invalid type ' + type); } coder = coderFixedBytes(size); } else { @@ -278,24 +282,24 @@ function getParamCoder(type) { break; case 'address': - if (coder) { throw new Error('invalid type ' + type); } + if (coder) { throwError('invalid type', { type: type }); } coder = coderAddress; break; case '[]': - if (!coder || coder.dynamic) { throw new Error('invalid type ' + type); } + if (!coder || coder.dynamic) { throwError('invalid type', { type: type }); } coder = coderArray(coder, -1); break; // "[0-9+]" 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]); coder = coderArray(coder, size); } } - if (!coder) { throw new Error('invalid type'); } + if (!coder) { throwError('invalid type'); } return coder; } @@ -325,7 +329,7 @@ function Interface(abi) { try { abi = JSON.parse(abi); } catch (error) { - throw new Error('invalid abi'); + throwError('invalid abi', { input: abi }); } } @@ -333,6 +337,10 @@ function Interface(abi) { defineFrozen(this, 'abi', abi); var methods = {}, events = {}, deploy = null; + + utils.defineProperty(this, 'functions', methods); + utils.defineProperty(this, 'events', events); + function addMethod(method) { switch (method.type) { @@ -341,14 +349,14 @@ function Interface(abi) { var inputTypes = getKeys(method.inputs, 'type'); var func = function(bytecode) { if (!utils.isHexString(bytecode)) { - throw new Error('invalid bytecode'); + throwError('invalid bytecode', {input: bytecode}); } var params = Array.prototype.slice.call(arguments, 1); if (params.length < inputTypes.length) { - throw new Error('missing parameter'); + throwError('missing parameter'); } else if (params.length > inputTypes.length) { - throw new Error('too many parameters'); + throwError('too many parameters'); } var result = { @@ -385,9 +393,9 @@ function Interface(abi) { var params = Array.prototype.slice.call(arguments, 0); if (params.length < inputTypes.length) { - throw new Error('missing parameter'); + throwError('missing parameter'); } 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); @@ -413,7 +421,7 @@ function Interface(abi) { 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); //} else if (this.fallbackFunction == null) { // utils.defineProperty(this, 'fallbackFunction', func); @@ -452,6 +460,10 @@ function Interface(abi) { break; + case 'fallback': + // Nothing to do for fallback + break; + default: console.log('WARNING: unsupported ABI type - ' + method.type); break; @@ -465,14 +477,12 @@ function Interface(abi) { addMethod({type: 'constructor', inputs: []}); } - utils.defineProperty(this, 'functions', methods); - utils.defineProperty(this, 'events', events); utils.defineProperty(this, 'deployFunction', deploy); } 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 = []; @@ -569,7 +579,7 @@ utils.defineProperty(Interface, 'decodeParams', function(names, types, data) { return values; }); -utils.defineProperty(Interface, 'getDeployTransaction', function(bytecode) { -}); +//utils.defineProperty(Interface, 'getDeployTransaction', function(bytecode) { +//}); module.exports = Interface;