From eddf93b2000e077cd7f37a8d035f5637d79a2327 Mon Sep 17 00:00:00 2001 From: Richard Moore Date: Wed, 10 Jan 2018 19:20:18 -0500 Subject: [PATCH] Added named parameters to tuple encoding. --- contracts/interface.js | 69 ++++++++++++++++++-------------- contracts/package.json | 2 +- tests/test-contract-interface.js | 27 ++++++++++--- 3 files changed, 62 insertions(+), 36 deletions(-) diff --git a/contracts/interface.js b/contracts/interface.js index f23bd5945..220c53cc9 100644 --- a/contracts/interface.js +++ b/contracts/interface.js @@ -262,11 +262,27 @@ function alignSize(size) { } function pack(coders, values) { + 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 }); + } + var parts = []; coders.forEach(function(coder, index) { parts.push({ dynamic: coder.dynamic, value: coder.encode(values[index]) }); - }) + }); var staticSize = 0, dynamicSize = 0; parts.forEach(function(part, index) { @@ -414,17 +430,6 @@ function coderTuple(coders, 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 }); - } - - // @TODO: If receiving an object, and we have names, create the array - - } else { - throwError('invalid value', { type: types, values: values }); - } - return pack(coders, value); }, decode: function(data, offset) { @@ -572,29 +577,27 @@ function Interface(abi) { switch (method.type) { case 'constructor': var func = (function() { - // @TODO: Move to parseParams - var inputTypes = getKeys(method.inputs, 'type'); + var inputParams = parseParams(method.inputs); var func = function(bytecode) { if (!utils.isHexString(bytecode)) { - throwError('invalid bytecode', {input: bytecode}); + throwError('invalid bytecode', { input: bytecode }); } var params = Array.prototype.slice.call(arguments, 1); - if (params.length < inputTypes.length) { + if (params.length < inputParams.types.length) { throwError('missing parameter'); - } else if (params.length > inputTypes.length) { + } else if (params.length > inputParams.types.length) { throwError('too many parameters'); } var result = { - bytecode: bytecode + Interface.encodeParams(inputTypes, params).substring(2), + bytecode: bytecode + Interface.encodeParams(inputParams.names, inputParams.types, params).substring(2), } return populateDescription(new DeployDescription(), result); } - // @TODO: Move to parseParams - defineFrozen(func, 'inputs', getKeys(method.inputs, 'name')); + defineFrozen(func, 'inputs', inputParams); return func; })(); @@ -608,7 +611,6 @@ function Interface(abi) { var inputParams = parseParams(method.inputs); var outputParams = parseParams(method.outputs); - var inputTypes = inputParams.types; if (method.constant) { var outputTypes = outputParams.types; var outputNames = outputParams.names; @@ -628,13 +630,13 @@ function Interface(abi) { var params = Array.prototype.slice.call(arguments, 0); - if (params.length < inputTypes.length) { + if (params.length < inputParams.types.length) { throwError('missing parameter'); - } else if (params.length > inputTypes.length) { + } else if (params.length > inputParams.types.length) { throwError('too many parameters'); } - result.data = sighash + Interface.encodeParams(inputTypes, params).substring(2); + result.data = sighash + Interface.encodeParams(inputParams.names, inputParams.types, params).substring(2); if (method.constant) { result.parse = function(data) { return Interface.decodeParams( @@ -649,9 +651,8 @@ 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')); + defineFrozen(func, 'inputs', inputParams); + defineFrozen(func, 'outputs', outputParams); utils.defineProperty(func, 'signature', signature); utils.defineProperty(func, 'sighash', sighash); @@ -803,12 +804,20 @@ function Interface(abi) { } -utils.defineProperty(Interface, 'encodeParams', function(types, values) { +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; + } + if (types.length !== values.length) { throwError('types/values mismatch', {types: types, values: values}); } var coders = []; - types.forEach(function(type) { - coders.push(getParamCoder(type)); + types.forEach(function(type, index) { + coders.push(getParamCoder(type, (names ? names[index]: undefined))); }); return utils.hexlify(coderTuple(coders).encode(values)); diff --git a/contracts/package.json b/contracts/package.json index 702979fc4..1ded472c4 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -1,6 +1,6 @@ { "name": "ethers-contracts", - "version": "2.1.10", + "version": "2.2.0", "description": "Contract and Interface (ABI) library for Ethereum.", "bugs": { "url": "http://github.com/ethers-io/ethers.js/issues", diff --git a/tests/test-contract-interface.js b/tests/test-contract-interface.js index 42d778608..a3a5d8494 100644 --- a/tests/test-contract-interface.js +++ b/tests/test-contract-interface.js @@ -95,11 +95,11 @@ function equals(actual, expected) { } -function getValues(object, format) { +function getValues(object, format, named) { if (Array.isArray(object)) { var result = []; object.forEach(function(object) { - result.push(getValues(object, format)); + result.push(getValues(object, format, named)); }); return result; } @@ -137,7 +137,15 @@ function getValues(object, format) { return utils.arrayify(object.value); case 'tuple': - return getValues(object.value, format); + var result = getValues(object.value, format, named); + if (named) { + var namedResult = {}; + result.forEach(function(value, index) { + namedResult['r' + String(index)] = value; + }); + return namedResult; + } + return result; default: throw new Error('invalid type - ' + object.type); @@ -236,6 +244,7 @@ describe('Contract Interface ABI v2 Decoding', function() { var Interface = require('../contracts/index.js').Interface; var tests = utils.loadTests('contract-interface-abi2'); + tests.forEach(function(test) { var values = getValues(JSON.parse(test.values)); var types = JSON.parse(test.types); @@ -255,17 +264,24 @@ describe('Contract Interface ABI v2 Encoding', function() { var tests = utils.loadTests('contract-interface-abi2'); tests.forEach(function(test) { var values = getValues(JSON.parse(test.values)); + var namedValues = getValues(JSON.parse(test.values), undefined, true); var types = JSON.parse(test.types); var expected = test.result; var title = test.name + ' => (' + test.types + ') = (' + test.value + ')'; - it(('encodes parameters - ' + test.name + ' - ' + test.types), function() { + it(('encodes ABIv2 parameters - ' + test.name + ' - ' + test.types), function() { var encoded = Interface.encodeParams(types, values); - assert.equal(encoded, expected, 'decoded parameters - ' + title); + assert.equal(encoded, expected, 'encoded positional parameters - ' + title); + + var contractInterface = new Interface(test.interface); + var outputNames = contractInterface.functions.test.outputs.names; + var namedEncoded = Interface.encodeParams(outputNames, types, values); + assert.equal(namedEncoded, expected, 'encoded named parameters - ' + title); }); }); }); +/* describe('Contract Interface ABI v2 Named Decoding', function() { var Interface = require('../contracts').Interface; @@ -283,6 +299,7 @@ describe('Contract Interface ABI v2 Named Decoding', function() { //console.dir(decoded, { depth: null }); }); }); +*/ describe('Test Contract Events', function() { var Interface = require('../contracts').Interface;