UNPKG

ethjs-abi

Version:

Just the Ethereum encoding and decoding methods from the ethers-io-wallet.

183 lines (148 loc) 5.67 kB
/* eslint-disable */ const utils = require('./utils/index.js'); const uint256Coder = utils.uint256Coder; const coderBoolean = utils.coderBoolean; const coderFixedBytes = utils.coderFixedBytes; const coderAddress = utils.coderAddress; const coderDynamicBytes = utils.coderDynamicBytes; const coderString = utils.coderString; const coderArray = utils.coderArray; const paramTypePart = utils.paramTypePart; const getParamCoder = utils.getParamCoder; function Result() {} function encodeParams(types, values) { if (types.length !== values.length) { throw new Error(`[ethjs-abi] while encoding params, types/values mismatch, Your contract requires ${types.length} types (arguments), and you passed in ${values.length}`); } var parts = []; types.forEach(function(type, index) { var coder = getParamCoder(type); parts.push({dynamic: coder.dynamic, value: coder.encode(values[index])}); }); function alignSize(size) { return parseInt(32 * Math.ceil(size / 32)); } var staticSize = 0, dynamicSize = 0; parts.forEach(function(part) { if (part.dynamic) { staticSize += 32; dynamicSize += alignSize(part.value.length); } else { staticSize += alignSize(part.value.length); } }); var offset = 0, dynamicOffset = staticSize; var data = new Buffer(staticSize + dynamicSize); parts.forEach(function(part, index) { if (part.dynamic) { uint256Coder.encode(dynamicOffset).copy(data, offset); offset += 32; part.value.copy(data, dynamicOffset); dynamicOffset += alignSize(part.value.length); } else { part.value.copy(data, offset); offset += alignSize(part.value.length); } }); return '0x' + data.toString('hex'); } // decode bytecode data from output names and types function decodeParams(names, types, data, useNumberedParams = true) { // Names is optional, so shift over all the parameters if not provided if (arguments.length < 3) { data = types; types = names; names = []; } data = utils.hexOrBuffer(data); var values = new Result(); var offset = 0; types.forEach(function(type, index) { var coder = getParamCoder(type); if (coder.dynamic) { var dynamicOffset = uint256Coder.decode(data, offset); var result = coder.decode(data, dynamicOffset.value.toNumber()); offset += dynamicOffset.consumed; } else { var result = coder.decode(data, offset); offset += result.consumed; } if (useNumberedParams) { values[index] = result.value; } if (names[index]) { values[names[index]] = result.value; } }); return values; } // create an encoded method signature from an ABI object function encodeSignature(method) { const signature = `${method.name}(${utils.getKeys(method.inputs, 'type').join(',')})`; const signatureEncoded = `0x${(new Buffer(utils.keccak256(signature), 'hex')).slice(0, 4).toString('hex')}`; return signatureEncoded; } // encode method ABI object with values in an array, output bytecode function encodeMethod(method, values) { const paramsEncoded = encodeParams(utils.getKeys(method.inputs, 'type'), values).substring(2); return `${encodeSignature(method)}${paramsEncoded}`; } // decode method data bytecode, from method ABI object function decodeMethod(method, data) { const outputNames = utils.getKeys(method.outputs, 'name', true); const outputTypes = utils.getKeys(method.outputs, 'type'); return decodeParams(outputNames, outputTypes, utils.hexOrBuffer(data)); } // decode method data bytecode, from method ABI object function encodeEvent(eventObject, values) { return encodeMethod(eventObject, values); } function eventSignature(eventObject) { const signature = `${eventObject.name}(${utils.getKeys(eventObject.inputs, 'type').join(',')})`; return `0x${utils.keccak256(signature)}`; } // decode method data bytecode, from method ABI object function decodeEvent(eventObject, data, topics, useNumberedParams = true) { const nonIndexed = eventObject.inputs.filter((input) => !input.indexed) const nonIndexedNames = utils.getKeys(nonIndexed, 'name', true); const nonIndexedTypes = utils.getKeys(nonIndexed, 'type'); const event = decodeParams(nonIndexedNames, nonIndexedTypes, utils.hexOrBuffer(data), useNumberedParams); const topicOffset = eventObject.anonymous ? 0 : 1; eventObject.inputs.filter((input) => input.indexed).map((input, i) => { const topic = new Buffer(topics[i + topicOffset].slice(2), 'hex'); const coder = getParamCoder(input.type); event[input.name] = coder.decode(topic, 0).value; }); event._eventName = eventObject.name; return event; } // Decode a specific log item with a specific event abi function decodeLogItem(eventObject, log, useNumberedParams = true) { if (eventObject && log.topics[0] === eventSignature(eventObject)) { return decodeEvent(eventObject, log.data, log.topics, useNumberedParams) } } // Create a decoder for all events defined in an abi. It returns a function which is called // on an array of log entries such as received from getLogs or getTransactionReceipt and parses // any matching log entries function logDecoder(abi, useNumberedParams = true) { const eventMap = {} abi.filter(item => item.type === 'event').map(item => { eventMap[eventSignature(item)] = item }) return function(logItems) { return logItems.map(log => decodeLogItem(eventMap[log.topics[0]], log, useNumberedParams)).filter(i => i) } } module.exports = { encodeParams, decodeParams, encodeMethod, decodeMethod, encodeEvent, decodeEvent, decodeLogItem, logDecoder, eventSignature, encodeSignature };