UNPKG

micro-eth-signer

Version:

Minimal library for Ethereum transactions, addresses and smart contracts

144 lines 5.25 kB
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