2019-05-14 18:25:46 -04:00
|
|
|
"use strict";
|
|
|
|
|
2019-08-01 18:04:06 -04:00
|
|
|
import { Logger } from "@ethersproject/logger";
|
|
|
|
import { version } from "../_version";
|
|
|
|
const logger = new Logger(version);
|
2019-05-14 18:25:46 -04:00
|
|
|
|
2020-05-12 22:41:04 -04:00
|
|
|
import { Coder, Reader, Result, Writer } from "./abstract-coder";
|
2019-05-14 18:25:46 -04:00
|
|
|
import { AnonymousCoder } from "./anonymous";
|
|
|
|
|
2020-05-12 22:41:04 -04:00
|
|
|
export function pack(writer: Writer, coders: ReadonlyArray<Coder>, values: Array<any> | { [ name: string ]: any }): number {
|
|
|
|
let arrayValues: Array<any> = null;
|
2019-05-14 18:25:46 -04:00
|
|
|
|
|
|
|
if (Array.isArray(values)) {
|
2020-05-12 22:41:04 -04:00
|
|
|
arrayValues = values;
|
2019-05-14 18:25:46 -04:00
|
|
|
|
|
|
|
} else if (values && typeof(values) === "object") {
|
2020-05-12 22:41:04 -04:00
|
|
|
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];
|
2019-05-14 18:25:46 -04:00
|
|
|
});
|
|
|
|
|
|
|
|
} else {
|
2019-08-01 18:04:06 -04:00
|
|
|
logger.throwArgumentError("invalid tuple value", "tuple", values);
|
2019-05-14 18:25:46 -04:00
|
|
|
}
|
|
|
|
|
2020-05-12 22:41:04 -04:00
|
|
|
if (coders.length !== arrayValues.length) {
|
2019-08-01 18:04:06 -04:00
|
|
|
logger.throwArgumentError("types/value length mismatch", "tuple", values);
|
2019-05-14 18:25:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
let staticWriter = new Writer(writer.wordSize);
|
|
|
|
let dynamicWriter = new Writer(writer.wordSize);
|
|
|
|
|
|
|
|
let updateFuncs: Array<(baseOffset: number) => void> = [];
|
|
|
|
coders.forEach((coder, index) => {
|
2020-05-12 22:41:04 -04:00
|
|
|
let value = arrayValues[index];
|
2019-05-14 18:25:46 -04:00
|
|
|
|
|
|
|
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); });
|
|
|
|
|
2020-10-05 15:34:00 -04:00
|
|
|
let length = writer.appendWriter(staticWriter);
|
|
|
|
length += writer.appendWriter(dynamicWriter);
|
2019-05-14 18:25:46 -04:00
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
2020-05-12 22:41:04 -04:00
|
|
|
export function unpack(reader: Reader, coders: Array<Coder>): Result {
|
2019-05-14 18:25:46 -04:00
|
|
|
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());
|
2020-04-25 03:25:42 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-05-14 18:25:46 -04:00
|
|
|
} else {
|
2020-04-25 03:25:42 -04:00
|
|
|
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;
|
|
|
|
}
|
2019-05-14 18:25:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (value != undefined) {
|
|
|
|
values.push(value);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-05-12 22:41:04 -04:00
|
|
|
// 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 }>{ });
|
|
|
|
|
2019-05-14 18:25:46 -04:00
|
|
|
// Add any named parameters (i.e. tuples)
|
|
|
|
coders.forEach((coder: Coder, index: number) => {
|
2020-05-12 22:41:04 -04:00
|
|
|
let name = coder.localName;
|
|
|
|
if (!name || uniqueNames[name] !== 1) { return; }
|
2019-05-14 18:25:46 -04:00
|
|
|
|
|
|
|
if (name === "length") { name = "_length"; }
|
|
|
|
|
|
|
|
if (values[name] != null) { return; }
|
|
|
|
|
2020-04-25 03:25:42 -04:00
|
|
|
const value = values[index];
|
|
|
|
|
|
|
|
if (value instanceof Error) {
|
|
|
|
Object.defineProperty(values, name, {
|
2021-08-24 16:03:12 -03:00
|
|
|
enumerable: true,
|
2020-04-25 03:25:42 -04:00
|
|
|
get: () => { throw value; }
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
values[name] = value;
|
|
|
|
}
|
2019-05-14 18:25:46 -04:00
|
|
|
});
|
|
|
|
|
2020-04-25 03:25:42 -04:00
|
|
|
for (let i = 0; i < values.length; i++) {
|
|
|
|
const value = values[i];
|
|
|
|
if (value instanceof Error) {
|
|
|
|
Object.defineProperty(values, i, {
|
2021-08-24 16:03:12 -03:00
|
|
|
enumerable: true,
|
2020-04-25 03:25:42 -04:00
|
|
|
get: () => { throw value; }
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-30 15:11:23 -04:00
|
|
|
return Object.freeze(values);
|
2019-05-14 18:25:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-11-23 00:59:44 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-05-14 18:25:46 -04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-08-20 15:33:16 -04:00
|
|
|
logger.checkArgumentCount(value.length, count, "coder array" + (this.localName? (" "+ this.localName): ""));
|
2019-05-14 18:25:46 -04:00
|
|
|
|
|
|
|
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();
|
|
|
|
|
2021-04-19 20:26:05 -04:00
|
|
|
// 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
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2019-05-14 18:25:46 -04:00
|
|
|
let coders = [];
|
|
|
|
for (let i = 0; i < count; i++) { coders.push(new AnonymousCoder(this.coder)); }
|
|
|
|
|
|
|
|
return reader.coerce(this.name, unpack(reader, coders));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|