ethers.js/packages/abi/lib.esm/coders/array.js

209 lines
7.2 KiB
JavaScript
Raw Permalink Normal View History

"use strict";
import { Logger } from "@ethersproject/logger";
import { version } from "../_version";
const logger = new Logger(version);
import { Coder, Writer } from "./abstract-coder";
import { AnonymousCoder } from "./anonymous";
export function pack(writer, coders, values) {
2020-05-12 23:31:51 -04:00
let arrayValues = null;
if (Array.isArray(values)) {
2020-05-12 23:31:51 -04:00
arrayValues = values;
}
else if (values && typeof (values) === "object") {
2020-05-12 23:31:51 -04:00
let unique = {};
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);
}
2020-05-12 23:31:51 -04:00
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 = [];
coders.forEach((coder, index) => {
2020-05-12 23:31:51 -04:00
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) => {
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:46:12 -04:00
let length = writer.appendWriter(staticWriter);
length += writer.appendWriter(dynamicWriter);
return length;
}
export function unpack(reader, coders) {
let values = [];
// A reader anchored to this base
let baseReader = reader.subReader(0);
coders.forEach((coder) => {
let value = null;
if (coder.dynamic) {
let offset = reader.readValue();
let offsetReader = baseReader.subReader(offset.toNumber());
2020-04-25 03:54:54 -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;
}
}
else {
2020-04-25 03:54:54 -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;
}
}
if (value != undefined) {
values.push(value);
}
});
2020-05-12 23:31:51 -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;
}, {});
// Add any named parameters (i.e. tuples)
coders.forEach((coder, index) => {
let name = coder.localName;
2020-05-12 23:31:51 -04:00
if (!name || uniqueNames[name] !== 1) {
return;
}
if (name === "length") {
name = "_length";
}
if (values[name] != null) {
return;
}
2020-04-25 03:54:54 -04:00
const value = values[index];
if (value instanceof Error) {
Object.defineProperty(values, name, {
2021-08-24 16:15:51 -03:00
enumerable: true,
2020-04-25 03:54:54 -04:00
get: () => { throw value; }
});
}
else {
values[name] = value;
}
});
2020-04-25 03:54:54 -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:15:51 -03:00
enumerable: true,
2020-04-25 03:54:54 -04:00
get: () => { throw value; }
});
}
}
2020-03-30 17:24:33 -04:00
return Object.freeze(values);
}
export class ArrayCoder extends Coder {
constructor(coder, length, localName) {
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 03:43:28 -05:00
defaultValue() {
// Verifies the child coder is valid (even if the array is dynamic or 0-length)
const defaultChild = this.coder.defaultValue();
const result = [];
for (let i = 0; i < this.length; i++) {
result.push(defaultChild);
}
return result;
}
encode(writer, value) {
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-25 01:53:48 -04:00
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) {
let count = this.length;
if (count === -1) {
count = reader.readValue().toNumber();
2021-04-19 21:30:28 -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
});
}
}
let coders = [];
for (let i = 0; i < count; i++) {
coders.push(new AnonymousCoder(this.coder));
}
return reader.coerce(this.name, unpack(reader, coders));
}
}
2020-07-13 08:03:56 -04:00
//# sourceMappingURL=array.js.map