UNPKG

@coinbase/wallet-sdk

Version:
274 lines (231 loc) 7.83 kB
// Extracted from https://github.com/ethereumjs/ethereumjs-abi and stripped out irrelevant code // Original code licensed under the MIT License - Copyright (c) 2015 Alex Beregszaszi /* eslint-disable */ //prettier-ignore const util = require('./util.cjs') // Convert from short to canonical names // FIXME: optimise or make this nicer? function elementaryName (name) { if (name.startsWith('int[')) { return 'int256' + name.slice(3) } else if (name === 'int') { return 'int256' } else if (name.startsWith('uint[')) { return 'uint256' + name.slice(4) } else if (name === 'uint') { return 'uint256' } else if (name.startsWith('fixed[')) { return 'fixed128x128' + name.slice(5) } else if (name === 'fixed') { return 'fixed128x128' } else if (name.startsWith('ufixed[')) { return 'ufixed128x128' + name.slice(6) } else if (name === 'ufixed') { return 'ufixed128x128' } return name } // Parse N from type<N> function parseTypeN (type) { return Number.parseInt(/^\D+(\d+)$/.exec(type)[1], 10) } // Parse N,M from type<N>x<M> function parseTypeNxM (type) { var tmp = /^\D+(\d+)x(\d+)$/.exec(type) return [ Number.parseInt(tmp[1], 10), Number.parseInt(tmp[2], 10) ] } // Parse N in type[<N>] where "type" can itself be an array type. function parseTypeArray (type) { var tmp = type.match(/(.*)\[(.*?)\]$/) if (tmp) { return tmp[2] === '' ? 'dynamic' : Number.parseInt(tmp[2], 10) } return null } function parseNumber (arg) { var type = typeof arg if (type === 'string' || type === 'number') { return BigInt(arg) } else if (type === 'bigint') { return arg } else { throw new Error('Argument is not a number') } } // Encodes a single item (can be dynamic array) // @returns: Buffer function encodeSingle (type, arg) { var size, num, ret, i if (type === 'address') { return encodeSingle('uint160', parseNumber(arg)) } else if (type === 'bool') { return encodeSingle('uint8', arg ? 1 : 0) } else if (type === 'string') { return encodeSingle('bytes', new Buffer(arg, 'utf8')) } else if (isArray(type)) { // this part handles fixed-length ([2]) and variable length ([]) arrays // NOTE: we catch here all calls to arrays, that simplifies the rest if (typeof arg.length === 'undefined') { throw new Error('Not an array?') } size = parseTypeArray(type) if (size !== 'dynamic' && size !== 0 && arg.length > size) { throw new Error('Elements exceed array size: ' + size) } ret = [] type = type.slice(0, type.lastIndexOf('[')) if (typeof arg === 'string') { arg = JSON.parse(arg) } for (i in arg) { ret.push(encodeSingle(type, arg[i])) } if (size === 'dynamic') { var length = encodeSingle('uint256', arg.length) ret.unshift(length) } return Buffer.concat(ret) } else if (type === 'bytes') { arg = new Buffer(arg) ret = Buffer.concat([ encodeSingle('uint256', arg.length), arg ]) if ((arg.length % 32) !== 0) { ret = Buffer.concat([ ret, util.zeros(32 - (arg.length % 32)) ]) } return ret } else if (type.startsWith('bytes')) { size = parseTypeN(type) if (size < 1 || size > 32) { throw new Error('Invalid bytes<N> width: ' + size) } return util.setLengthRight(arg, 32) } else if (type.startsWith('uint')) { size = parseTypeN(type) if ((size % 8) || (size < 8) || (size > 256)) { throw new Error('Invalid uint<N> width: ' + size) } num = parseNumber(arg) const bitLength = util.bitLengthFromBigInt(num) if (bitLength > size) { throw new Error('Supplied uint exceeds width: ' + size + ' vs ' + bitLength) } if (num < 0) { throw new Error('Supplied uint is negative') } return util.bufferBEFromBigInt(num, 32); } else if (type.startsWith('int')) { size = parseTypeN(type) if ((size % 8) || (size < 8) || (size > 256)) { throw new Error('Invalid int<N> width: ' + size) } num = parseNumber(arg) const bitLength = util.bitLengthFromBigInt(num) if (bitLength > size) { throw new Error('Supplied int exceeds width: ' + size + ' vs ' + bitLength) } const twos = util.twosFromBigInt(num, 256); return util.bufferBEFromBigInt(twos, 32); } else if (type.startsWith('ufixed')) { size = parseTypeNxM(type) num = parseNumber(arg) if (num < 0) { throw new Error('Supplied ufixed is negative') } return encodeSingle('uint256', num * BigInt(2) ** BigInt(size[1])) } else if (type.startsWith('fixed')) { size = parseTypeNxM(type) return encodeSingle('int256', parseNumber(arg) * BigInt(2) ** BigInt(size[1])) } throw new Error('Unsupported or invalid type: ' + type) } // Is a type dynamic? function isDynamic (type) { // FIXME: handle all types? I don't think anything is missing now return (type === 'string') || (type === 'bytes') || (parseTypeArray(type) === 'dynamic') } // Is a type an array? function isArray (type) { return type.lastIndexOf(']') === type.length - 1 } // Encode a method/event with arguments // @types an array of string type names // @args an array of the appropriate values function rawEncode (types, values) { var output = [] var data = [] var headLength = 32 * types.length for (var i in types) { var type = elementaryName(types[i]) var value = values[i] var cur = encodeSingle(type, value) // Use the head/tail method for storing dynamic data if (isDynamic(type)) { output.push(encodeSingle('uint256', headLength)) data.push(cur) headLength += cur.length } else { output.push(cur) } } return Buffer.concat(output.concat(data)) } function solidityPack (types, values) { if (types.length !== values.length) { throw new Error('Number of types are not matching the values') } var size, num var ret = [] for (var i = 0; i < types.length; i++) { var type = elementaryName(types[i]) var value = values[i] if (type === 'bytes') { ret.push(value) } else if (type === 'string') { ret.push(new Buffer(value, 'utf8')) } else if (type === 'bool') { ret.push(new Buffer(value ? '01' : '00', 'hex')) } else if (type === 'address') { ret.push(util.setLength(value, 20)) } else if (type.startsWith('bytes')) { size = parseTypeN(type) if (size < 1 || size > 32) { throw new Error('Invalid bytes<N> width: ' + size) } ret.push(util.setLengthRight(value, size)) } else if (type.startsWith('uint')) { size = parseTypeN(type) if ((size % 8) || (size < 8) || (size > 256)) { throw new Error('Invalid uint<N> width: ' + size) } num = parseNumber(value) const bitLength = util.bitLengthFromBigInt(num) if (bitLength > size) { throw new Error('Supplied uint exceeds width: ' + size + ' vs ' + bitLength) } ret.push(util.bufferBEFromBigInt(num, size / 8)) } else if (type.startsWith('int')) { size = parseTypeN(type) if ((size % 8) || (size < 8) || (size > 256)) { throw new Error('Invalid int<N> width: ' + size) } num = parseNumber(value) const bitLength = util.bitLengthFromBigInt(num) if (bitLength > size) { throw new Error('Supplied int exceeds width: ' + size + ' vs ' + bitLength) } const twos = util.twosFromBigInt(num, size); ret.push(util.bufferBEFromBigInt(twos, size / 8)) } else { // FIXME: support all other types throw new Error('Unsupported or invalid type: ' + type) } } return Buffer.concat(ret) } function soliditySHA3 (types, values) { return util.keccak(solidityPack(types, values)) } module.exports = { rawEncode, solidityPack, soliditySHA3 }