Socket
Socket
Sign inDemoInstall

@findeth/abi

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@findeth/abi - npm Package Compare versions

Comparing version 0.1.0 to 0.2.0

lib/cjs/parsers/parser.js

111

lib/cjs/abi.js

@@ -6,110 +6,43 @@ "use strict";

});
exports.iterate = iterate;
exports.encodeWithIdentifier = exports.encode = exports.encodeValue = exports.decode = exports.decodeValue = exports.PARSERS_BY_TYPE = void 0;
exports.decode = exports.encodeWithIdentifier = exports.encode = void 0;
var _identifier = require("./identifier");
var _address = require("./parsers/address");
var _array = require("./parsers/array");
var _bytes = require("./parsers/bytes");
var _buffer = require("./utils/buffer");
var _number = require("./parsers/number");
const encode = (input, values) => {
const types = input.map(type => {
if (typeof type === 'string') {
return type;
}
var _string = require("./parsers/string");
const PARSERS_BY_TYPE = {
address: {
encode: _address.encodeAddress,
decode: _address.decodeAddress
},
bytes: {
encode: _bytes.encodeBytes,
decode: _bytes.decodeBytes
},
string: {
encode: _string.encodeString,
decode: _string.decodeString
}
return type.type;
});
return (0, _array.pack)(Buffer.alloc(0), values, types);
};
exports.PARSERS_BY_TYPE = PARSERS_BY_TYPE;
function* iterate(buffer, chunkSize) {
for (let i = 0; i < buffer.length; i += chunkSize) {
yield buffer.slice(i, i + chunkSize);
}
exports.encode = encode;
return buffer;
}
const decodeValue = (value, data, type) => {
const parser = PARSERS_BY_TYPE[type];
if (parser) {
return parser.decode(value, data, type);
}
if ((0, _number.isNumber)(type)) {
return (0, _number.decodeNumber)(value, data, type);
}
if ((0, _array.isArray)(type)) {
return (0, _array.decodeArray)(value, data, type);
}
throw new Error(`Cannot parse value with type ${type}`);
const encodeWithIdentifier = (contractFunction, values) => {
const identifier = Buffer.from((0, _identifier.getIdentifier)(contractFunction), 'hex');
const encoded = encode(contractFunction.inputs, values);
return (0, _buffer.concat)(identifier, encoded);
};
exports.decodeValue = decodeValue;
exports.encodeWithIdentifier = encodeWithIdentifier;
const decode = (types, data) => {
const iterator = iterate(data, 32);
return types.map(input => {
const {
value,
done
} = iterator.next();
if (done) {
throw new Error('Invalid input data: incorrect length');
const decode = (input, buffer) => {
const types = input.map(type => {
if (typeof type === 'string') {
return type;
}
return decodeValue(value, data, input.type);
return type.type;
});
return (0, _array.unpack)(buffer, types);
};
exports.decode = decode;
const encodeValue = (target, value, position, type) => {
const parser = PARSERS_BY_TYPE[type];
if (parser) {
return parser.encode(target, value, position, type);
}
if ((0, _number.isNumber)(type)) {
return (0, _number.encodeNumber)(target, value, position, type);
}
return target;
};
exports.encodeValue = encodeValue;
const encode = (types, data) => {
return types.reduce((target, input, index) => {
return encodeValue(target, data[index], index * 32, input.type);
}, Buffer.alloc(0));
};
exports.encode = encode;
const encodeWithIdentifier = (contractFunction, data) => {
const identifier = Buffer.from((0, _identifier.getIdentifier)(contractFunction), 'hex');
const encoded = encode(contractFunction.inputs, data);
return Buffer.concat([identifier, encoded]);
};
exports.encodeWithIdentifier = encodeWithIdentifier;
//# sourceMappingURL=abi.js.map

@@ -10,4 +10,8 @@ "use strict";

const isTuple = input => {
return input.type === 'tuple';
};
const parseType = input => {
if (input.components) {
if (isTuple(input)) {
return `(${input.components.map(parseType)})`;

@@ -14,0 +18,0 @@ }

@@ -6,14 +6,7 @@ "use strict";

});
Object.defineProperty(exports, "decode", {
enumerable: true,
get: function () {
return _abi.decode;
}
});
Object.defineProperty(exports, "decodeValue", {
enumerable: true,
get: function () {
return _abi.decodeValue;
}
});
var _exportNames = {
encode: true,
encodeWithIdentifier: true,
decode: true
};
Object.defineProperty(exports, "encode", {

@@ -25,12 +18,12 @@ enumerable: true,

});
Object.defineProperty(exports, "encodeValue", {
Object.defineProperty(exports, "encodeWithIdentifier", {
enumerable: true,
get: function () {
return _abi.encodeValue;
return _abi.encodeWithIdentifier;
}
});
Object.defineProperty(exports, "encodeWithIdentifier", {
Object.defineProperty(exports, "decode", {
enumerable: true,
get: function () {
return _abi.encodeWithIdentifier;
return _abi.decode;
}

@@ -40,2 +33,15 @@ });

var _abi = require("./abi");
var _contract = require("./contract");
Object.keys(_contract).forEach(function (key) {
if (key === "default" || key === "__esModule") return;
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
Object.defineProperty(exports, key, {
enumerable: true,
get: function () {
return _contract[key];
}
});
});
//# sourceMappingURL=index.js.map

@@ -6,4 +6,14 @@ "use strict";

});
exports.encodeAddress = exports.decodeAddress = void 0;
exports.decodeAddress = exports.encodeAddress = void 0;
var _buffer = require("../utils/buffer");
const encodeAddress = (buffer, value) => {
const addressBuffer = Buffer.alloc(32);
addressBuffer.write(value.substring(2), 12, 'hex');
return (0, _buffer.concat)(buffer, addressBuffer);
};
exports.encodeAddress = encodeAddress;
const decodeAddress = value => {

@@ -15,10 +25,2 @@ const addressBuffer = value.subarray(-20);

exports.decodeAddress = decodeAddress;
const encodeAddress = (target, data, position) => {
const addressBuffer = Buffer.alloc(32);
addressBuffer.write(data.substring(2), 12, 'hex');
return Buffer.concat([target.slice(0, position), addressBuffer, target.subarray(position)]);
};
exports.encodeAddress = encodeAddress;
//# sourceMappingURL=address.js.map

@@ -6,13 +6,12 @@ "use strict";

});
exports.encodeArray = exports.decodeArray = exports.isArray = void 0;
exports.iterate = iterate;
exports.unpack = exports.pack = exports.getParser = exports.decodeArray = exports.encodeArray = exports.isArray = void 0;
var _bigintBuffer = require("bigint-buffer");
var _buffer = require("../utils/buffer");
var _abi = require("../abi");
var _address = require("./address");
const ARRAY_REGEX = /(.*)\[]/;
var _number = require("./number");
const getType = type => {
return type.match(ARRAY_REGEX)[1];
};
const ARRAY_REGEX = /^(.*)\[]$/;

@@ -25,27 +24,132 @@ const isArray = type => {

const decodeArray = (value, data, type) => {
const innerType = getType(type);
const pointer = Number((0, _bigintBuffer.toBigIntBE)(value));
const length = Number((0, _bigintBuffer.toBigIntBE)(data.subarray(pointer, pointer + 32)));
return new Array(length).fill(undefined).map((_, index) => index * 32 + 32).map(itemPointer => {
const itemValue = data.subarray(pointer + itemPointer, pointer + itemPointer + 32);
return (0, _abi.decodeValue)(itemValue, data, innerType);
});
const getType = type => {
return type.match(ARRAY_REGEX)[1];
};
const encodeArray = (buffer, values, type) => {
const actualType = getType(type);
const length = (0, _buffer.toBuffer)(values.length);
const arrayBuffer = (0, _buffer.concat)(buffer, length);
return pack(arrayBuffer, values, new Array(values.length).fill(actualType));
};
exports.encodeArray = encodeArray;
const decodeArray = (value, buffer, type) => {
const actualType = getType(type);
const pointer = Number((0, _buffer.toNumber)(value));
const length = Number((0, _buffer.toNumber)(buffer.subarray(pointer, pointer + 32)));
const arrayPointer = pointer + 32;
const arrayBuffer = buffer.subarray(arrayPointer);
return unpack(arrayBuffer, new Array(length).fill(actualType));
};
exports.decodeArray = decodeArray;
const parsers = {
array: {
dynamic: true,
encode: encodeArray,
decode: decodeArray
},
address: {
encode: _address.encodeAddress,
decode: _address.decodeAddress
},
number: {
encode: _number.encodeNumber,
decode: _number.decodeNumber
}
};
const encodeArray = (target, data, position, type) => {
const innerType = getType(type);
const pointer = (0, _bigintBuffer.toBufferBE)(BigInt(target.length) + 32n, 32);
const length = (0, _bigintBuffer.toBufferBE)(BigInt(data.length), 32);
const first = Buffer.concat([target.subarray(0, position), pointer, target.subarray(position), length]);
const encodedData = data.map(item => {
const buffer = Buffer.alloc(0);
return (0, _abi.encodeValue)(buffer, item, 0, innerType);
const getParser = type => {
if (parsers[type]) {
return parsers[type];
}
if ((0, _number.isNumber)(type)) {
return parsers.number;
}
if (isArray(type)) {
return parsers.array;
}
throw new Error(`type "${type}" is not supported`);
};
exports.getParser = getParser;
const pack = (buffer, values, types) => {
const {
staticBuffer: packedStaticBuffer,
dynamicBuffer: packedDynamicBuffer,
updateFunctions: packedUpdateFunctions
} = types.reduce(({
staticBuffer,
dynamicBuffer,
updateFunctions
}, type, index) => {
const parser = getParser(type);
const value = values[index];
if (parser.dynamic) {
const offset = dynamicBuffer.length;
const staticOffset = staticBuffer.length;
const newStaticBuffer = (0, _buffer.concat)(staticBuffer, Buffer.alloc(32, 0));
const newDynamicBuffer = parser.encode(dynamicBuffer, value, type);
const update = oldBuffer => {
return Buffer.concat([oldBuffer.subarray(0, staticOffset), (0, _buffer.toBuffer)(oldBuffer.length + offset), oldBuffer.subarray(staticOffset + 32)]);
};
return {
staticBuffer: newStaticBuffer,
dynamicBuffer: newDynamicBuffer,
updateFunctions: [...updateFunctions, update]
};
}
const newBuffer = parser.encode(staticBuffer, value, type);
return {
staticBuffer: newBuffer,
dynamicBuffer,
updateFunctions
};
}, {
staticBuffer: Buffer.alloc(0),
dynamicBuffer: Buffer.alloc(0),
updateFunctions: []
});
return Buffer.concat([first, ...encodedData]);
const updatedStaticBuffer = packedUpdateFunctions.reduce((target, update) => update(target), packedStaticBuffer);
return Buffer.concat([buffer, updatedStaticBuffer, packedDynamicBuffer]);
};
exports.encodeArray = encodeArray;
exports.pack = pack;
function* iterate(buffer, chunkSize) {
for (let i = 0; i < buffer.length; i += chunkSize) {
yield buffer.slice(i, i + chunkSize);
}
return buffer;
}
const unpack = (buffer, types) => {
const iterator = iterate(buffer, 32);
return types.map(type => {
const {
value,
done
} = iterator.next();
if (done) {
throw new Error('input data has an invalid length');
}
const parser = getParser(type);
return parser.decode(value, buffer, type);
});
};
exports.unpack = unpack;
//# sourceMappingURL=array.js.map

@@ -6,9 +6,9 @@ "use strict";

});
exports.encodeNumber = exports.decodeNumber = exports.inRange = exports.getBitLength = exports.isNumber = void 0;
exports.decodeNumber = exports.encodeNumber = exports.inRange = exports.getBitLength = exports.isNumber = void 0;
var _bigintBuffer = require("bigint-buffer");
var _buffer = require("../utils/buffer");
var _buffer = require("../buffer");
var _twosComplement = require("../utils/twos-complement");
const NUMBER_REGEX = /u?int([0-9]*)?/;
const NUMBER_REGEX = /^u?int([0-9]*)?$/;

@@ -48,47 +48,25 @@ const isSigned = type => {

const fromTwosComplement = buffer => {
let value = 0n;
for (const byte of buffer) {
value = (value << 8n) + BigInt(byte);
const encodeNumber = (buffer, value, type) => {
if (!inRange(value, type)) {
throw new Error(`Cannot encode number: value is out of range for type ${type}`);
}
return BigInt.asIntN(buffer.length * 8, value);
};
const decodeNumber = (value, _, type) => {
if (isSigned(type)) {
return fromTwosComplement(value);
return (0, _buffer.concat)(buffer, (0, _twosComplement.toTwosComplement)(value, 32));
}
return (0, _bigintBuffer.toBigIntBE)(value);
return (0, _buffer.concat)(buffer, (0, _buffer.toBuffer)(value));
};
exports.decodeNumber = decodeNumber;
exports.encodeNumber = encodeNumber;
const toTwosComplement = (value, length) => {
const buffer = Buffer.alloc(length);
for (let i = 0; i < buffer.length; i++) {
buffer[i] = Number(BigInt.asUintN(8, value));
value = value >> 8n;
}
return buffer.reverse();
};
const encodeNumber = (target, data, position, type) => {
if (!inRange(data, type)) {
throw new Error(`Cannot encode number: value is out of range for type ${type}`);
}
const decodeNumber = (value, _, type) => {
if (isSigned(type)) {
return (0, _buffer.concat)(target, toTwosComplement(data, 32), position);
return (0, _twosComplement.fromTwosComplement)(value);
}
const numberBuffer = (0, _bigintBuffer.toBufferBE)(data, 32);
return (0, _buffer.concat)(target, numberBuffer, position);
return (0, _buffer.toNumber)(value);
};
exports.encodeNumber = encodeNumber;
exports.decodeNumber = decodeNumber;
//# sourceMappingURL=number.js.map
import { getIdentifier } from './identifier';
import { decodeAddress, encodeAddress } from './parsers/address';
import { decodeArray, isArray } from './parsers/array';
import { decodeBytes, encodeBytes } from './parsers/bytes';
import { decodeNumber, encodeNumber, isNumber } from './parsers/number';
import { decodeString, encodeString } from './parsers/string';
export const PARSERS_BY_TYPE = {
address: {
encode: encodeAddress,
decode: decodeAddress
},
bytes: {
encode: encodeBytes,
decode: decodeBytes
},
string: {
encode: encodeString,
decode: decodeString
}
};
export function* iterate(buffer, chunkSize) {
for (let i = 0; i < buffer.length; i += chunkSize) {
yield buffer.slice(i, i + chunkSize);
}
import { pack, unpack } from './parsers/array';
import { concat } from './utils/buffer';
export const encode = (input, values) => {
const types = input.map(type => {
if (typeof type === 'string') {
return type;
}
return buffer;
}
export const decodeValue = (value, data, type) => {
const parser = PARSERS_BY_TYPE[type];
if (parser) {
return parser.decode(value, data, type);
}
if (isNumber(type)) {
return decodeNumber(value, data, type);
}
if (isArray(type)) {
return decodeArray(value, data, type);
}
throw new Error(`Cannot parse value with type ${type}`);
return type.type;
});
return pack(Buffer.alloc(0), values, types);
};
export const decode = (types, data) => {
const iterator = iterate(data, 32);
return types.map(input => {
const {
value,
done
} = iterator.next();
if (done) {
throw new Error('Invalid input data: incorrect length');
export const encodeWithIdentifier = (contractFunction, values) => {
const identifier = Buffer.from(getIdentifier(contractFunction), 'hex');
const encoded = encode(contractFunction.inputs, values);
return concat(identifier, encoded);
};
export const decode = (input, buffer) => {
const types = input.map(type => {
if (typeof type === 'string') {
return type;
}
return decodeValue(value, data, input.type);
return type.type;
});
return unpack(buffer, types);
};
export const encodeValue = (target, value, position, type) => {
const parser = PARSERS_BY_TYPE[type];
if (parser) {
return parser.encode(target, value, position, type);
}
if (isNumber(type)) {
return encodeNumber(target, value, position, type);
}
return target;
};
export const encode = (types, data) => {
return types.reduce((target, input, index) => {
return encodeValue(target, data[index], index * 32, input.type);
}, Buffer.alloc(0));
};
export const encodeWithIdentifier = (contractFunction, data) => {
const identifier = Buffer.from(getIdentifier(contractFunction), 'hex');
const encoded = encode(contractFunction.inputs, data);
return Buffer.concat([identifier, encoded]);
};
//# sourceMappingURL=abi.js.map
import { keccak256 } from './utils/keccak256';
const isTuple = input => {
return input.type === 'tuple';
};
export const parseType = input => {
if (input.components) {
if (isTuple(input)) {
return `(${input.components.map(parseType)})`;

@@ -5,0 +10,0 @@ }

@@ -1,2 +0,3 @@

export { decode, decodeValue, encode, encodeValue, encodeWithIdentifier } from './abi';
export { encode, encodeWithIdentifier, decode } from './abi';
export * from './contract';
//# sourceMappingURL=index.js.map

@@ -0,1 +1,7 @@

import { concat } from '../utils/buffer';
export const encodeAddress = (buffer, value) => {
const addressBuffer = Buffer.alloc(32);
addressBuffer.write(value.substring(2), 12, 'hex');
return concat(buffer, addressBuffer);
};
export const decodeAddress = value => {

@@ -5,7 +11,2 @@ const addressBuffer = value.subarray(-20);

};
export const encodeAddress = (target, data, position) => {
const addressBuffer = Buffer.alloc(32);
addressBuffer.write(data.substring(2), 12, 'hex');
return Buffer.concat([target.slice(0, position), addressBuffer, target.subarray(position)]);
};
//# sourceMappingURL=address.js.map

@@ -1,4 +0,8 @@

import { toBigIntBE, toBufferBE } from 'bigint-buffer';
import { decodeValue, encodeValue } from '../abi';
const ARRAY_REGEX = /(.*)\[]/;
import { concat, toBuffer, toNumber } from '../utils/buffer';
import { decodeAddress, encodeAddress } from './address';
import { decodeNumber, encodeNumber, isNumber } from './number';
const ARRAY_REGEX = /^(.*)\[]$/;
export const isArray = type => {
return ARRAY_REGEX.test(type);
};

@@ -9,25 +13,113 @@ const getType = type => {

export const isArray = type => {
return ARRAY_REGEX.test(type);
export const encodeArray = (buffer, values, type) => {
const actualType = getType(type);
const length = toBuffer(values.length);
const arrayBuffer = concat(buffer, length);
return pack(arrayBuffer, values, new Array(values.length).fill(actualType));
};
export const decodeArray = (value, data, type) => {
const innerType = getType(type);
const pointer = Number(toBigIntBE(value));
const length = Number(toBigIntBE(data.subarray(pointer, pointer + 32)));
return new Array(length).fill(undefined).map((_, index) => index * 32 + 32).map(itemPointer => {
const itemValue = data.subarray(pointer + itemPointer, pointer + itemPointer + 32);
return decodeValue(itemValue, data, innerType);
export const decodeArray = (value, buffer, type) => {
const actualType = getType(type);
const pointer = Number(toNumber(value));
const length = Number(toNumber(buffer.subarray(pointer, pointer + 32)));
const arrayPointer = pointer + 32;
const arrayBuffer = buffer.subarray(arrayPointer);
return unpack(arrayBuffer, new Array(length).fill(actualType));
};
const parsers = {
array: {
dynamic: true,
encode: encodeArray,
decode: decodeArray
},
address: {
encode: encodeAddress,
decode: decodeAddress
},
number: {
encode: encodeNumber,
decode: decodeNumber
}
};
export const getParser = type => {
if (parsers[type]) {
return parsers[type];
}
if (isNumber(type)) {
return parsers.number;
}
if (isArray(type)) {
return parsers.array;
}
throw new Error(`type "${type}" is not supported`);
};
export const pack = (buffer, values, types) => {
const {
staticBuffer: packedStaticBuffer,
dynamicBuffer: packedDynamicBuffer,
updateFunctions: packedUpdateFunctions
} = types.reduce(({
staticBuffer,
dynamicBuffer,
updateFunctions
}, type, index) => {
const parser = getParser(type);
const value = values[index];
if (parser.dynamic) {
const offset = dynamicBuffer.length;
const staticOffset = staticBuffer.length;
const newStaticBuffer = concat(staticBuffer, Buffer.alloc(32, 0));
const newDynamicBuffer = parser.encode(dynamicBuffer, value, type);
const update = oldBuffer => {
return Buffer.concat([oldBuffer.subarray(0, staticOffset), toBuffer(oldBuffer.length + offset), oldBuffer.subarray(staticOffset + 32)]);
};
return {
staticBuffer: newStaticBuffer,
dynamicBuffer: newDynamicBuffer,
updateFunctions: [...updateFunctions, update]
};
}
const newBuffer = parser.encode(staticBuffer, value, type);
return {
staticBuffer: newBuffer,
dynamicBuffer,
updateFunctions
};
}, {
staticBuffer: Buffer.alloc(0),
dynamicBuffer: Buffer.alloc(0),
updateFunctions: []
});
const updatedStaticBuffer = packedUpdateFunctions.reduce((target, update) => update(target), packedStaticBuffer);
return Buffer.concat([buffer, updatedStaticBuffer, packedDynamicBuffer]);
};
export const encodeArray = (target, data, position, type) => {
const innerType = getType(type);
const pointer = toBufferBE(BigInt(target.length) + 32n, 32);
const length = toBufferBE(BigInt(data.length), 32);
const first = Buffer.concat([target.subarray(0, position), pointer, target.subarray(position), length]);
const encodedData = data.map(item => {
const buffer = Buffer.alloc(0);
return encodeValue(buffer, item, 0, innerType);
export function* iterate(buffer, chunkSize) {
for (let i = 0; i < buffer.length; i += chunkSize) {
yield buffer.slice(i, i + chunkSize);
}
return buffer;
}
export const unpack = (buffer, types) => {
const iterator = iterate(buffer, 32);
return types.map(type => {
const {
value,
done
} = iterator.next();
if (done) {
throw new Error('input data has an invalid length');
}
const parser = getParser(type);
return parser.decode(value, buffer, type);
});
return Buffer.concat([first, ...encodedData]);
};
//# sourceMappingURL=array.js.map

@@ -1,4 +0,4 @@

import { toBigIntBE, toBufferBE } from 'bigint-buffer';
import { concat } from '../buffer';
const NUMBER_REGEX = /u?int([0-9]*)?/;
import { concat, toBuffer, toNumber } from '../utils/buffer';
import { fromTwosComplement, toTwosComplement } from '../utils/twos-complement';
const NUMBER_REGEX = /^u?int([0-9]*)?$/;

@@ -29,13 +29,13 @@ const isSigned = type => {

};
export const encodeNumber = (buffer, value, type) => {
if (!inRange(value, type)) {
throw new Error(`Cannot encode number: value is out of range for type ${type}`);
}
const fromTwosComplement = buffer => {
let value = 0n;
for (const byte of buffer) {
value = (value << 8n) + BigInt(byte);
if (isSigned(type)) {
return concat(buffer, toTwosComplement(value, 32));
}
return BigInt.asIntN(buffer.length * 8, value);
return concat(buffer, toBuffer(value));
};
export const decodeNumber = (value, _, type) => {

@@ -46,28 +46,4 @@ if (isSigned(type)) {

return toBigIntBE(value);
return toNumber(value);
};
const toTwosComplement = (value, length) => {
const buffer = Buffer.alloc(length);
for (let i = 0; i < buffer.length; i++) {
buffer[i] = Number(BigInt.asUintN(8, value));
value = value >> 8n;
}
return buffer.reverse();
};
export const encodeNumber = (target, data, position, type) => {
if (!inRange(data, type)) {
throw new Error(`Cannot encode number: value is out of range for type ${type}`);
}
if (isSigned(type)) {
return concat(target, toTwosComplement(data, 32), position);
}
const numberBuffer = toBufferBE(data, 32);
return concat(target, numberBuffer, position);
};
//# sourceMappingURL=number.js.map
{
"name": "@findeth/abi",
"version": "0.1.0",
"version": "0.2.0",
"description": "A tiny Solidity ABI encoder and decoder",

@@ -5,0 +5,0 @@ "author": "Maarten Zuidhoorn <maarten@zuidhoorn.com>",

@@ -1,7 +0,35 @@

import { decode, encode, encodeWithIdentifier } from './abi';
import { ContractFunction } from './contract';
import erc20 from './__tests__/erc20.json';
import { decode, encode } from './abi';
const transferInterface = erc20[7] as ContractFunction;
describe('encode', () => {
it('encodes simple values', () => {
expect(encode(['uint256', 'uint256'], [12345, 12345]).toString('hex')).toBe(
'00000000000000000000000000000000000000000000000000000000000030390000000000000000000000000000000000000000000000000000000000003039'
);
});
it('encodes array values', () => {
expect(encode(['uint256', 'uint256[]', 'uint256'], [12345, [67890, 67890], 12345]).toString('hex')).toBe(
'000000000000000000000000000000000000000000000000000000000000303900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000003039000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000109320000000000000000000000000000000000000000000000000000000000010932'
);
});
it('encodes nested values', () => {
expect(
encode(
['uint256', 'uint256[][]', 'uint256'],
[
12345,
[
[54321, 12345],
[67890, 98760]
],
12345
]
).toString('hex')
).toBe(
'0000000000000000000000000000000000000000000000000000000000003039000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000030390000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000d43100000000000000000000000000000000000000000000000000000000000030390000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000001093200000000000000000000000000000000000000000000000000000000000181c8'
);
});
});
describe('decode', () => {

@@ -13,3 +41,3 @@ it('decodes a token transfer', () => {

);
expect(decode(transferInterface.inputs, buffer)).toStrictEqual([
expect(decode(['address', 'uint256'], buffer)).toStrictEqual([
'0x6b175474e89094c44da98b954eedeac495271d0f',

@@ -19,22 +47,10 @@ 12345n

});
});
describe('encode', () => {
it('encodes a token transfer', () => {
expect(
encode(transferInterface.inputs, ['0x6b175474e89094c44da98b954eedeac495271d0f', 12345n]).toString('hex')
).toStrictEqual(
'0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000000000000000003039'
it('decodes array values', () => {
const buffer = Buffer.from(
'000000000000000000000000000000000000000000000000000000000000303900000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000003039000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000109320000000000000000000000000000000000000000000000000000000000010932',
'hex'
);
expect(decode(['uint256', 'uint256[]', 'uint256'], buffer)).toStrictEqual([12345n, [67890n, 67890n], 12345n]);
});
});
describe('encodeWithIdentifier', () => {
it('encodes a token transfer with identifier', () => {
expect(
encodeWithIdentifier(transferInterface, ['0x6b175474e89094c44da98b954eedeac495271d0f', 12345n]).toString('hex')
).toStrictEqual(
'a9059cbb0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000000000000000003039'
);
});
});

@@ -1,132 +0,49 @@

import { ContractFunction, ContractInput, DecodeFunction, EncodeFunction, Type } from './contract';
import { ContractFunction, ContractInput } from './contract';
import { getIdentifier } from './identifier';
import { decodeAddress, encodeAddress } from './parsers/address';
import { decodeArray, isArray } from './parsers/array';
import { decodeBytes, encodeBytes } from './parsers/bytes';
import { decodeNumber, encodeNumber, isNumber } from './parsers/number';
import { decodeString, encodeString } from './parsers/string';
import { pack, unpack } from './parsers/array';
import { concat } from './utils/buffer';
// tslint:disable-next-line:no-any
export const PARSERS_BY_TYPE: { [key in Type]?: { decode: DecodeFunction<any>; encode: EncodeFunction<any> } } = {
address: {
encode: encodeAddress,
decode: decodeAddress
},
bytes: {
encode: encodeBytes,
decode: decodeBytes
},
string: {
encode: encodeString,
decode: decodeString
}
};
/**
* Iterate over a `Buffer` with provided `chunkSize`.
* Encode the input data with the provided types.
*
* @param {Buffer} buffer
* @param {number} chunkSize
* @return {Generator<Buffer, Buffer, void>}
* @param {(ContractInput | string)[]} input
* @param {unknown[]} values
* @return {Buffer}
*/
export function* iterate(buffer: Buffer, chunkSize: number): Generator<Buffer, Buffer, void> {
for (let i = 0; i < buffer.length; i += chunkSize) {
yield buffer.slice(i, i + chunkSize);
}
return buffer;
}
/**
* Parse a raw value from a `Buffer`.
*
* @param {Buffer} value
* @param {Buffer} data
* @param {string} type
* @return {unknown}
*/
export const decodeValue = (value: Buffer, data: Buffer, type: string): unknown => {
const parser = PARSERS_BY_TYPE[type as Type];
if (parser) {
return parser.decode(value, data, type);
}
if (isNumber(type)) {
return decodeNumber(value, data, type);
}
if (isArray(type)) {
return decodeArray(value, data, type);
}
throw new Error(`Cannot parse value with type ${type}`);
};
/**
* Decode an input data `Buffer` with provided types.
*
* @param {ContractInput[]} types
* @param {Buffer} data
* @return {unknown[]}
*/
export const decode = (types: ContractInput[], data: Buffer): unknown[] => {
const iterator = iterate(data, 32);
return types.map((input) => {
const { value, done } = iterator.next();
if (done) {
throw new Error('Invalid input data: incorrect length');
export const encode = (input: (ContractInput | string)[], values: unknown[]): Buffer => {
const types = input.map((type) => {
if (typeof type === 'string') {
return type;
}
return decodeValue(value, data, input.type);
return type.type;
});
return pack(Buffer.alloc(0), values, types);
};
/**
* Encode `value` to a Buffer, and return the new full input data `Buffer`.
* Encode the input data with the provided types, and prepend the function identifier.
*
* @param {Buffer} target
* @param {any} value
* @param {number} position
* @param {string} type
* @param {ContractFunction} contractFunction
* @param {unknown[]} values
* @return {Buffer}
*/
export const encodeValue = (target: Buffer, value: unknown, position: number, type: string): Buffer => {
const parser = PARSERS_BY_TYPE[type as Type];
if (parser) {
return parser.encode(target, value, position, type);
}
export const encodeWithIdentifier = (contractFunction: ContractFunction, values: unknown[]): Buffer => {
const identifier = Buffer.from(getIdentifier(contractFunction), 'hex');
const encoded = encode(contractFunction.inputs, values);
if (isNumber(type as Type)) {
return encodeNumber(target, value as bigint, position, type);
}
return target;
return concat(identifier, encoded);
};
/**
* Encode the input data with the provided types.
*
* @param {ContractInput[]} types
* @param {unknown[]} data
* @return {Buffer}
*/
export const encode = (types: ContractInput[], data: unknown[]): Buffer => {
return types.reduce<Buffer>((target, input, index) => {
return encodeValue(target, data[index], index * 32, input.type);
}, Buffer.alloc(0));
};
export const decode = <T extends unknown[]>(input: (ContractInput | string)[], buffer: Buffer): T => {
const types = input.map((type) => {
if (typeof type === 'string') {
return type;
}
/**
* Encode the input data with the provided function, and prepend the function identifier.
*
* @param {ContractFunction} contractFunction
* @param {unknown[]} data
* @return {Buffer}
*/
export const encodeWithIdentifier = (contractFunction: ContractFunction, data: unknown[]) => {
const identifier = Buffer.from(getIdentifier(contractFunction), 'hex');
const encoded = encode(contractFunction.inputs, data);
return type.type;
});
return Buffer.concat([identifier, encoded]);
return unpack(buffer, types) as T;
};

@@ -24,3 +24,2 @@ /**

type: Type | string;
components: undefined;
}

@@ -27,0 +26,0 @@

@@ -1,4 +0,8 @@

import { ContractFunction, ContractInput } from './contract';
import { ContractFunction, ContractInput, ContractInputTuple } from './contract';
import { keccak256 } from './utils/keccak256';
const isTuple = (input: ContractInput): input is ContractInputTuple => {
return input.type === 'tuple';
};
/**

@@ -11,3 +15,3 @@ * Parse the type of a contract input to a `string`.

export const parseType = (input: ContractInput): string => {
if (input.components) {
if (isTuple(input)) {
return `(${input.components.map(parseType)})`;

@@ -14,0 +18,0 @@ }

@@ -1,1 +0,2 @@

export { decode, decodeValue, encode, encodeValue, encodeWithIdentifier } from './abi';
export { encode, encodeWithIdentifier, decode } from './abi';
export * from './contract';

@@ -1,13 +0,14 @@

import { DecodeFunction, EncodeFunction } from '../contract';
import { concat } from '../utils/buffer';
import { DecodeFunction, EncodeFunction } from './parser';
export const decodeAddress: DecodeFunction<string> = (value): string => {
export const encodeAddress: EncodeFunction = (buffer: Buffer, value: string): Buffer => {
const addressBuffer = Buffer.alloc(32);
addressBuffer.write(value.substring(2), 12, 'hex');
return concat(buffer, addressBuffer);
};
export const decodeAddress: DecodeFunction = (value: Buffer): string => {
const addressBuffer = value.subarray(-20);
return `0x${addressBuffer.toString('hex')}`;
};
export const encodeAddress: EncodeFunction<string> = (target, data, position): Buffer => {
const addressBuffer = Buffer.alloc(32);
addressBuffer.write(data.substring(2), 12, 'hex');
return Buffer.concat([target.slice(0, position), addressBuffer, target.subarray(position)]);
};

@@ -1,7 +0,24 @@

import { toBigIntBE, toBufferBE } from 'bigint-buffer';
import { decodeValue, encodeValue } from '../abi';
import { DecodeFunction, EncodeFunction } from '../contract';
import { concat, toBuffer, toNumber } from '../utils/buffer';
import { decodeAddress, encodeAddress } from './address';
import { decodeNumber, encodeNumber, isNumber } from './number';
import { DecodeFunction, EncodeFunction, Parser } from './parser';
const ARRAY_REGEX = /(.*)\[]/;
const ARRAY_REGEX = /^(.*)\[]$/;
/**
* Check if a type is an array type.
*
* @param {string} type
* @return {boolean}
*/
export const isArray = (type: string): boolean => {
return ARRAY_REGEX.test(type);
};
/**
* Get the "inner" type for an array type. E.g. `getType("uint256[]")` -> uint256.
*
* @param {string} type
* @return {string}
*/
const getType = (type: string): string => {

@@ -11,33 +28,155 @@ return type.match(ARRAY_REGEX)![1];

export const isArray = (type: string): boolean => {
return ARRAY_REGEX.test(type);
export const encodeArray: EncodeFunction = (buffer: Buffer, values: unknown[], type: string): Buffer => {
const actualType = getType(type);
const length = toBuffer(values.length);
const arrayBuffer = concat(buffer, length);
return pack(arrayBuffer, values, new Array(values.length).fill(actualType));
};
export const decodeArray: DecodeFunction<unknown[]> = (value: Buffer, data: Buffer, type: string): unknown[] => {
const innerType = getType(type);
const pointer = Number(toBigIntBE(value));
const length = Number(toBigIntBE(data.subarray(pointer, pointer + 32)));
export const decodeArray: DecodeFunction = (value: Buffer, buffer: Buffer, type: string): unknown[] => {
const actualType = getType(type);
const pointer = Number(toNumber(value));
const length = Number(toNumber(buffer.subarray(pointer, pointer + 32)));
return new Array<undefined>(length)
.fill(undefined)
.map((_, index) => index * 32 + 32)
.map((itemPointer) => {
const itemValue = data.subarray(pointer + itemPointer, pointer + itemPointer + 32);
return decodeValue(itemValue, data, innerType);
});
const arrayPointer = pointer + 32;
const arrayBuffer = buffer.subarray(arrayPointer);
return unpack(arrayBuffer, new Array(length).fill(actualType));
};
export const encodeArray: EncodeFunction<unknown[]> = (target, data, position, type): Buffer => {
const innerType = getType(type);
const pointer = toBufferBE(BigInt(target.length) + 32n, 32);
const length = toBufferBE(BigInt(data.length), 32);
/**
* All available parsers.
*/
const parsers: Record<string, Parser> = {
array: {
dynamic: true,
encode: encodeArray,
decode: decodeArray
},
address: {
encode: encodeAddress,
decode: decodeAddress
},
number: {
encode: encodeNumber,
decode: decodeNumber
}
};
const first = Buffer.concat([target.subarray(0, position), pointer, target.subarray(position), length]);
const encodedData = data.map((item) => {
// TODO: This currently doesn't work with nested array types
const buffer = Buffer.alloc(0);
return encodeValue(buffer, item, 0, innerType);
});
/**
* Get a parser for a type. Throws an error if the parser could not be found.
*
* @param {string} type
* @return {Parser}
*/
export const getParser = (type: string): Parser => {
if (parsers[type]) {
return parsers[type];
}
return Buffer.concat([first, ...encodedData]);
if (isNumber(type)) {
return parsers.number;
}
if (isArray(type)) {
return parsers.array;
}
throw new Error(`type "${type}" is not supported`);
};
interface PackState {
staticBuffer: Buffer;
dynamicBuffer: Buffer;
updateFunctions: ((buffer: Buffer) => Buffer)[];
}
/**
* Pack multiple values into a single Buffer, based on the provided types. Returns a new buffer with the
* packed values.
*
* Based on the implementation of Ethers.js:
* https://github.com/ethers-io/ethers.js/blob/fa87417e9416d99a37d9a2668a1e54feb7e342fc/packages/abi/src.ts/coders/array.ts
*
* @param {Buffer} buffer
* @param {any[]} values
* @param {string[]} types
* @return {Buffer}
*/
export const pack = (buffer: Buffer, values: unknown[], types: string[]): Buffer => {
const {
staticBuffer: packedStaticBuffer,
dynamicBuffer: packedDynamicBuffer,
updateFunctions: packedUpdateFunctions
} = types.reduce<PackState>(
({ staticBuffer, dynamicBuffer, updateFunctions }, type, index) => {
const parser = getParser(type);
const value = values[index];
if (parser.dynamic) {
const offset = dynamicBuffer.length;
const staticOffset = staticBuffer.length;
const newStaticBuffer = concat(staticBuffer, Buffer.alloc(32, 0));
const newDynamicBuffer = parser.encode(dynamicBuffer, value, type);
const update = (oldBuffer: Buffer): Buffer => {
return Buffer.concat([
oldBuffer.subarray(0, staticOffset),
toBuffer(oldBuffer.length + offset),
oldBuffer.subarray(staticOffset + 32)
]);
};
return {
staticBuffer: newStaticBuffer,
dynamicBuffer: newDynamicBuffer,
updateFunctions: [...updateFunctions, update]
};
}
const newBuffer = parser.encode(staticBuffer, value, type);
return { staticBuffer: newBuffer, dynamicBuffer, updateFunctions };
},
{ staticBuffer: Buffer.alloc(0), dynamicBuffer: Buffer.alloc(0), updateFunctions: [] }
);
const updatedStaticBuffer = packedUpdateFunctions.reduce<Buffer>(
(target, update) => update(target),
packedStaticBuffer
);
return Buffer.concat([buffer, updatedStaticBuffer, packedDynamicBuffer]);
};
/**
* Iterate over a `Buffer` with provided `chunkSize`.
*
* @param {Buffer} buffer
* @param {number} chunkSize
* @return {Generator<Buffer, Buffer, void>}
*/
export function* iterate(buffer: Buffer, chunkSize: number): Generator<Buffer, Buffer, void> {
for (let i = 0; i < buffer.length; i += chunkSize) {
yield buffer.slice(i, i + chunkSize);
}
return buffer;
}
export const unpack = (buffer: Buffer, types: string[]): unknown[] => {
const iterator = iterate(buffer, 32);
return types.map((type) => {
const { value, done } = iterator.next();
if (done) {
throw new Error('input data has an invalid length');
}
const parser = getParser(type);
return parser.decode(value, buffer, type);
});
};

@@ -1,6 +0,6 @@

import { toBigIntBE, toBufferBE } from 'bigint-buffer';
import { concat } from '../buffer';
import { DecodeFunction, EncodeFunction } from '../contract';
import { concat, toBuffer, toNumber } from '../utils/buffer';
import { fromTwosComplement, toTwosComplement } from '../utils/twos-complement';
import { DecodeFunction, EncodeFunction } from './parser';
const NUMBER_REGEX = /u?int([0-9]*)?/;
const NUMBER_REGEX = /^u?int([0-9]*)?$/;

@@ -32,42 +32,20 @@ const isSigned = (type: string): boolean => {

const fromTwosComplement = (buffer: Buffer): bigint => {
let value = 0n;
for (const byte of buffer) {
// tslint:disable-next-line:no-bitwise
value = (value << 8n) + BigInt(byte);
export const encodeNumber: EncodeFunction = (buffer: Buffer, value: bigint, type: string): Buffer => {
if (!inRange(value, type)) {
throw new Error(`Cannot encode number: value is out of range for type ${type}`);
}
return BigInt.asIntN(buffer.length * 8, value);
};
export const decodeNumber: DecodeFunction<bigint> = (value, _, type): bigint => {
if (isSigned(type)) {
return fromTwosComplement(value);
return concat(buffer, toTwosComplement(value, 32));
}
return toBigIntBE(value);
return concat(buffer, toBuffer(value));
};
const toTwosComplement = (value: bigint, length: number): Buffer => {
const buffer = Buffer.alloc(length);
for (let i = 0; i < buffer.length; i++) {
buffer[i] = Number(BigInt.asUintN(8, value));
// tslint:disable-next-line:no-bitwise
value = value >> 8n;
}
return buffer.reverse();
};
export const encodeNumber: EncodeFunction<bigint> = (target, data, position, type): Buffer => {
if (!inRange(data, type)) {
throw new Error(`Cannot encode number: value is out of range for type ${type}`);
}
export const decodeNumber: DecodeFunction = (value: Buffer, _, type: string): bigint => {
if (isSigned(type)) {
return concat(target, toTwosComplement(data, 32), position);
return fromTwosComplement(value);
}
const numberBuffer = toBufferBE(data, 32);
return concat(target, numberBuffer, position);
return toNumber(value);
};
/// <reference types="node" />
import { ContractFunction, ContractInput, DecodeFunction, EncodeFunction, Type } from './contract';
export declare const PARSERS_BY_TYPE: {
[key in Type]?: {
decode: DecodeFunction<any>;
encode: EncodeFunction<any>;
};
};
import { ContractFunction, ContractInput } from './contract';
/**
* Iterate over a `Buffer` with provided `chunkSize`.
*
* @param {Buffer} buffer
* @param {number} chunkSize
* @return {Generator<Buffer, Buffer, void>}
*/
export declare function iterate(buffer: Buffer, chunkSize: number): Generator<Buffer, Buffer, void>;
/**
* Parse a raw value from a `Buffer`.
*
* @param {Buffer} value
* @param {Buffer} data
* @param {string} type
* @return {unknown}
*/
export declare const decodeValue: (value: Buffer, data: Buffer, type: string) => unknown;
/**
* Decode an input data `Buffer` with provided types.
*
* @param {ContractInput[]} types
* @param {Buffer} data
* @return {unknown[]}
*/
export declare const decode: (types: ContractInput[], data: Buffer) => unknown[];
/**
* Encode `value` to a Buffer, and return the new full input data `Buffer`.
*
* @param {Buffer} target
* @param {any} value
* @param {number} position
* @param {string} type
* @return {Buffer}
*/
export declare const encodeValue: (target: Buffer, value: unknown, position: number, type: string) => Buffer;
/**
* Encode the input data with the provided types.
*
* @param {ContractInput[]} types
* @param {unknown[]} data
* @param {(ContractInput | string)[]} input
* @param {unknown[]} values
* @return {Buffer}
*/
export declare const encode: (types: ContractInput[], data: unknown[]) => Buffer;
export declare const encode: (input: (ContractInput | string)[], values: unknown[]) => Buffer;
/**
* Encode the input data with the provided function, and prepend the function identifier.
* Encode the input data with the provided types, and prepend the function identifier.
*
* @param {ContractFunction} contractFunction
* @param {unknown[]} data
* @param {unknown[]} values
* @return {Buffer}
*/
export declare const encodeWithIdentifier: (contractFunction: ContractFunction, data: unknown[]) => Buffer;
export declare const encodeWithIdentifier: (contractFunction: ContractFunction, values: unknown[]) => Buffer;
export declare const decode: <T extends unknown[]>(input: (ContractInput | string)[], buffer: Buffer) => T;

@@ -13,3 +13,2 @@ /// <reference types="node" />

type: Type | string;
components: undefined;
}

@@ -16,0 +15,0 @@ /**

@@ -1,1 +0,2 @@

export { decode, decodeValue, encode, encodeValue, encodeWithIdentifier } from './abi';
export { encode, encodeWithIdentifier, decode } from './abi';
export * from './contract';

@@ -1,3 +0,3 @@

import { DecodeFunction, EncodeFunction } from '../contract';
export declare const decodeAddress: DecodeFunction<string>;
export declare const encodeAddress: EncodeFunction<string>;
import { DecodeFunction, EncodeFunction } from './parser';
export declare const encodeAddress: EncodeFunction;
export declare const decodeAddress: DecodeFunction;

@@ -1,4 +0,40 @@

import { DecodeFunction, EncodeFunction } from '../contract';
/// <reference types="node" />
import { DecodeFunction, EncodeFunction, Parser } from './parser';
/**
* Check if a type is an array type.
*
* @param {string} type
* @return {boolean}
*/
export declare const isArray: (type: string) => boolean;
export declare const decodeArray: DecodeFunction<unknown[]>;
export declare const encodeArray: EncodeFunction<unknown[]>;
export declare const encodeArray: EncodeFunction;
export declare const decodeArray: DecodeFunction;
/**
* Get a parser for a type. Throws an error if the parser could not be found.
*
* @param {string} type
* @return {Parser}
*/
export declare const getParser: (type: string) => Parser;
/**
* Pack multiple values into a single Buffer, based on the provided types. Returns a new buffer with the
* packed values.
*
* Based on the implementation of Ethers.js:
* https://github.com/ethers-io/ethers.js/blob/fa87417e9416d99a37d9a2668a1e54feb7e342fc/packages/abi/src.ts/coders/array.ts
*
* @param {Buffer} buffer
* @param {any[]} values
* @param {string[]} types
* @return {Buffer}
*/
export declare const pack: (buffer: Buffer, values: unknown[], types: string[]) => Buffer;
/**
* Iterate over a `Buffer` with provided `chunkSize`.
*
* @param {Buffer} buffer
* @param {number} chunkSize
* @return {Generator<Buffer, Buffer, void>}
*/
export declare function iterate(buffer: Buffer, chunkSize: number): Generator<Buffer, Buffer, void>;
export declare const unpack: (buffer: Buffer, types: string[]) => unknown[];

@@ -1,6 +0,6 @@

import { DecodeFunction, EncodeFunction } from '../contract';
import { DecodeFunction, EncodeFunction } from './parser';
export declare const isNumber: (type: string) => boolean;
export declare const getBitLength: (type: string) => number;
export declare const inRange: (value: bigint, type: string) => boolean;
export declare const decodeNumber: DecodeFunction<bigint>;
export declare const encodeNumber: EncodeFunction<bigint>;
export declare const encodeNumber: EncodeFunction;
export declare const decodeNumber: DecodeFunction;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc