@coinbase/wallet-sdk
Version:
Coinbase Wallet JavaScript SDK
274 lines (231 loc) • 7.83 kB
JavaScript
// 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
}