micro-eth-signer
Version:
Minimal library for Ethereum transactions, addresses and smart contracts
144 lines • 5.25 kB
JavaScript
import * as P from 'micro-packed';
export const ARRAY_RE = /(.+)(\[(\d+)?\])$/; // TODO: is this correct?
function EPad(p) {
return P.padLeft(32, p, P.ZeroPad);
}
const PTR = EPad(P.U32BE);
const U256BE_LEN = PTR;
// Main difference between regular array: length stored outside and offsets calculated without length
function ethArray(inner) {
return P.wrap({
size: undefined,
encodeStream: (w, value) => {
U256BE_LEN.encodeStream(w, value.length);
w.bytes(P.array(value.length, inner).encode(value));
},
decodeStream: (r) => P.array(U256BE_LEN.decodeStream(r), inner).decodeStream(r.offsetReader(r.pos)),
});
}
// Because u32 in eth is not real u32, just U256BE with limits...
const ethInt = (bits, signed = false) => {
if (!Number.isSafeInteger(bits) || bits <= 0 || bits % 8 !== 0 || bits > 256)
throw new Error('ethInt: invalid numeric type');
const _bits = BigInt(bits);
const inner = P.bigint(32, false, signed);
return P.validate(P.wrap({
size: inner.size,
encodeStream: (w, value) => inner.encodeStream(w, value),
decodeStream: (r) => inner.decodeStream(r),
}), (value) => {
// TODO: validate useful for narrowing types, need to add support in types?
if (typeof value === 'number')
value = BigInt(value);
P.utils.checkBounds(value, _bits, !!signed);
return value;
});
};
// Ugly hack, because tuple of pointers considered "dynamic" without any reason.
function isDyn(args) {
let res = false;
if (Array.isArray(args)) {
for (let arg of args)
if (arg.size === undefined)
res = true;
}
else {
for (let arg in args)
if (args[arg].size === undefined)
res = true;
}
return res;
}
// NOTE: we need as const if we want to access string as values inside types :(
export function mapComponent(c) {
// Arrays (should be first one, since recursive)
let m;
if ((m = ARRAY_RE.exec(c.type))) {
const inner = mapComponent({ ...c, type: m[1] });
if (inner.size === 0)
throw new Error('mapComponent: arrays of zero-size elements disabled (possible DoS attack)');
// Static array
if (m[3] !== undefined) {
const m3 = Number.parseInt(m[3]);
if (!Number.isSafeInteger(m3))
throw new Error(`mapComponent: wrong array size=${m[3]}`);
let out = P.array(m3, inner);
// Static array of dynamic values should be behind pointer too, again without reason.
if (inner.size === undefined)
out = P.pointer(PTR, out);
return out;
}
else {
// Dynamic array
return P.pointer(PTR, ethArray(inner));
}
}
if (c.type === 'tuple') {
const components = c.components;
let hasNames = true;
const args = [];
for (let comp of components) {
if (!comp.name)
hasNames = false;
args.push(mapComponent(comp));
}
let out;
// If there is names for all fields -- return struct, otherwise tuple
if (hasNames) {
const struct = {};
for (const arg of components) {
if (struct[arg.name])
throw new Error(`mapType: same field name=${arg.name}`);
struct[arg.name] = mapComponent(arg);
}
out = P.struct(struct);
}
else
out = P.tuple(args);
// If tuple has dynamic elements it becomes dynamic too, without reason.
if (isDyn(args))
out = P.pointer(PTR, out);
return out;
}
if (c.type === 'string')
return P.pointer(PTR, P.padRight(32, P.string(U256BE_LEN), P.ZeroPad));
if (c.type === 'bytes')
return P.pointer(PTR, P.padRight(32, P.bytes(U256BE_LEN), P.ZeroPad));
if (c.type === 'address')
return EPad(P.hex(20, { isLE: false, with0x: true }));
if (c.type === 'bool')
return EPad(P.bool);
if ((m = /^(u?)int([0-9]+)?$/.exec(c.type)))
return ethInt(m[2] ? +m[2] : 256, m[1] !== 'u');
if ((m = /^bytes([0-9]{1,2})$/.exec(c.type))) {
const parsed = +m[1];
if (!parsed || parsed > 32)
throw new Error('wrong bytes<N> type');
return P.padRight(32, P.bytes(parsed), P.ZeroPad);
}
throw new Error(`mapComponent: unknown component=${c}`);
}
// Because args and output are not tuple
// TODO: try merge with mapComponent
export function mapArgs(args) {
// More ergonomic input/output
if (args.length === 1)
return mapComponent(args[0]);
let hasNames = true;
for (const arg of args)
if (!arg.name)
hasNames = false;
if (hasNames) {
const out = {};
for (const arg of args) {
const name = arg.name;
if (out[name])
throw new Error(`mapArgs: same field name=${name}`);
out[name] = mapComponent(arg);
}
return P.struct(out);
}
else
return P.tuple(args.map(mapComponent));
}
//# sourceMappingURL=abi-mapper.js.map