UNPKG

saepenatus

Version:

Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardised spec compliant web3 providers for all supported wallets, framework agnostic modern javascript UI with code splitting, CSS customization, mul

237 lines (187 loc) 7.55 kB
"use strict"; import { Logger } from "@ethersproject/logger"; import { version } from "../_version"; const logger = new Logger(version); import { Coder, Reader, Result, Writer } from "./abstract-coder"; import { AnonymousCoder } from "./anonymous"; export function pack(writer: Writer, coders: ReadonlyArray<Coder>, values: Array<any> | { [ name: string ]: any }): number { let arrayValues: Array<any> = null; if (Array.isArray(values)) { arrayValues = values; } else if (values && typeof(values) === "object") { let unique: { [ name: string ]: boolean } = { }; arrayValues = coders.map((coder) => { const name = coder.localName; if (!name) { logger.throwError("cannot encode object for signature with missing names", Logger.errors.INVALID_ARGUMENT, { argument: "values", coder: coder, value: values }); } if (unique[name]) { logger.throwError("cannot encode object for signature with duplicate names", Logger.errors.INVALID_ARGUMENT, { argument: "values", coder: coder, value: values }); } unique[name] = true; return values[name]; }); } else { logger.throwArgumentError("invalid tuple value", "tuple", values); } if (coders.length !== arrayValues.length) { logger.throwArgumentError("types/value length mismatch", "tuple", values); } let staticWriter = new Writer(writer.wordSize); let dynamicWriter = new Writer(writer.wordSize); let updateFuncs: Array<(baseOffset: number) => void> = []; coders.forEach((coder, index) => { let value = arrayValues[index]; if (coder.dynamic) { // Get current dynamic offset (for the future pointer) let dynamicOffset = dynamicWriter.length; // Encode the dynamic value into the dynamicWriter coder.encode(dynamicWriter, value); // Prepare to populate the correct offset once we are done let updateFunc = staticWriter.writeUpdatableValue(); updateFuncs.push((baseOffset: number) => { updateFunc(baseOffset + dynamicOffset); }); } else { coder.encode(staticWriter, value); } }); // Backfill all the dynamic offsets, now that we know the static length updateFuncs.forEach((func) => { func(staticWriter.length); }); let length = writer.appendWriter(staticWriter); length += writer.appendWriter(dynamicWriter); return length; } export function unpack(reader: Reader, coders: Array<Coder>): Result { let values: any = []; // A reader anchored to this base let baseReader = reader.subReader(0); coders.forEach((coder) => { let value: any = null; if (coder.dynamic) { let offset = reader.readValue(); let offsetReader = baseReader.subReader(offset.toNumber()); try { value = coder.decode(offsetReader); } catch (error) { // Cannot recover from this if (error.code === Logger.errors.BUFFER_OVERRUN) { throw error; } value = error; value.baseType = coder.name; value.name = coder.localName; value.type = coder.type; } } else { try { value = coder.decode(reader); } catch (error) { // Cannot recover from this if (error.code === Logger.errors.BUFFER_OVERRUN) { throw error; } value = error; value.baseType = coder.name; value.name = coder.localName; value.type = coder.type; } } if (value != undefined) { values.push(value); } }); // We only output named properties for uniquely named coders const uniqueNames = coders.reduce((accum, coder) => { const name = coder.localName; if (name) { if (!accum[name]) { accum[name] = 0; } accum[name]++; } return accum; }, <{ [ name: string ]: number }>{ }); // Add any named parameters (i.e. tuples) coders.forEach((coder: Coder, index: number) => { let name = coder.localName; if (!name || uniqueNames[name] !== 1) { return; } if (name === "length") { name = "_length"; } if (values[name] != null) { return; } const value = values[index]; if (value instanceof Error) { Object.defineProperty(values, name, { enumerable: true, get: () => { throw value; } }); } else { values[name] = value; } }); for (let i = 0; i < values.length; i++) { const value = values[i]; if (value instanceof Error) { Object.defineProperty(values, i, { enumerable: true, get: () => { throw value; } }); } } return Object.freeze(values); } export class ArrayCoder extends Coder { readonly coder: Coder; readonly length: number; constructor(coder: Coder, length: number, localName: string) { const type = (coder.type + "[" + (length >= 0 ? length: "") + "]"); const dynamic = (length === -1 || coder.dynamic); super("array", type, localName, dynamic); this.coder = coder; this.length = length; } defaultValue(): Array<any> { // Verifies the child coder is valid (even if the array is dynamic or 0-length) const defaultChild = this.coder.defaultValue(); const result: Array<any> = []; for (let i = 0; i < this.length; i++) { result.push(defaultChild); } return result; } encode(writer: Writer, value: Array<any>): number { if (!Array.isArray(value)) { this._throwError("expected array value", value); } let count = this.length; if (count === -1) { count = value.length; writer.writeValue(value.length); } logger.checkArgumentCount(value.length, count, "coder array" + (this.localName? (" "+ this.localName): "")); let coders = []; for (let i = 0; i < value.length; i++) { coders.push(this.coder); } return pack(writer, coders, value); } decode(reader: Reader): any { let count = this.length; if (count === -1) { count = reader.readValue().toNumber(); // Check that there is *roughly* enough data to ensure // stray random data is not being read as a length. Each // slot requires at least 32 bytes for their value (or 32 // bytes as a link to the data). This could use a much // tighter bound, but we are erroring on the side of safety. if (count * 32 > reader._data.length) { logger.throwError("insufficient data length", Logger.errors.BUFFER_OVERRUN, { length: reader._data.length, count: count }); } } let coders = []; for (let i = 0; i < count; i++) { coders.push(new AnonymousCoder(this.coder)); } return reader.coerce(this.name, unpack(reader, coders)); } }