All files / ethers.js/src.ts/abi/coders array.ts

74.24% Statements 147/198
78.57% Branches 22/28
85.71% Functions 6/7
74.24% Lines 147/198

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 2001x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 6296x 6296x 6296x 6296x 6296x 6296x                                     6296x 6296x 6296x 6296x 6296x 6296x 6296x 6296x 15070x 15070x 15070x 4544x 4544x 4544x 4544x 4544x 4544x 4544x 4544x 4544x 4544x 4544x 4544x 15070x 10526x 10526x 6296x 6296x 6296x 6296x 6296x 6296x 6296x 6296x 6296x 1x 1x 1x 1x 1x 6427x 6427x 6427x 6427x 6427x 6427x 6427x 15163x 15163x 15163x 4754x 4754x 4754x 4754x 4754x                     4754x 15163x 10409x 10409x 10409x                     10409x 15163x 15163x     15163x 15163x 15163x 6427x 6427x 6427x 6427x 1x 1x 1x 1x 1x 2404x 2404x 2404x 2404x 2404x 2404x 2404x 2404x 2404x 2404x 2404x                   2404x 2404x 2243x 2243x 2243x     2243x 2243x 2243x 2243x 481x 481x 481x 2243x 2243x 2243x 2243x 2243x 2243x 2243x 2243x 2404x 2404x 2243x 2243x 481x 481x 481x 481x 481x 481x 481x 481x 481x 481x 2243x 2243x 2243x 2243x 2243x 2404x    
import {
    defineProperties, isError, assert, assertArgument, assertArgumentCount
} from "../../utils/index.js";
 
import { Typed } from "../typed.js";
 
import { Coder, Result, WordSize, Writer } from "./abstract-coder.js";
import { AnonymousCoder } from "./anonymous.js";
 
import type { Reader } from "./abstract-coder.js";
 
/**
 *  @_ignore
 */
export function pack(writer: Writer, coders: ReadonlyArray<Coder>, values: Array<any> | { [ name: string ]: any }): number {
    let arrayValues: Array<any> = [ ];
 
    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;
            assert(name, "cannot encode object for signature with missing names",
                "INVALID_ARGUMENT", { argument: "values", info: { coder }, value: values });

            assert(!unique[name], "cannot encode object for signature with duplicate names",
                "INVALID_ARGUMENT", { argument: "values", info: { coder }, value: values });

            unique[name] = true;

            return values[name];
        });

    } else {
        assertArgument(false, "invalid tuple value", "tuple", values);
    }
 
    assertArgument(coders.length === arrayValues.length, "types/value length mismatch", "tuple", values);
 
    let staticWriter = new Writer();
    let dynamicWriter = new Writer();
 
    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;
}
 
/**
 *  @_ignore
 */
export function unpack(reader: Reader, coders: ReadonlyArray<Coder>): Result {
    let values: Array<any> = [];
    let keys: Array<null | string> = [ ];
 
    // A reader anchored to this base
    let baseReader = reader.subReader(0);
 
    coders.forEach((coder) => {
        let value: any = null;
 
        if (coder.dynamic) {
            let offset = reader.readIndex();
            let offsetReader = baseReader.subReader(offset);
            try {
                value = coder.decode(offsetReader);
            } catch (error: any) {
                // Cannot recover from this
                if (isError(error, "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: any) {
                // Cannot recover from this
                if (isError(error, "BUFFER_OVERRUN")) {
                    throw error;
                }

                value = error;
                value.baseType = coder.name;
                value.name = coder.localName;
                value.type = coder.type;
            }
        }
 
        if (value == undefined) {
            throw new Error("investigate");
        }
 
        values.push(value);
        keys.push(coder.localName || null);
    });
 
    return Result.fromItems(values, keys);
}
 
/**
 *  @_ignore
 */
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);
        defineProperties<ArrayCoder>(this, { coder, 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> | Typed): number {
        const value = Typed.dereference(_value, "array");
 
        if(!Array.isArray(value)) {
            this._throwError("expected array value", value);
        }
 
        let count = this.length;
 
        if (count === -1) {
            count = value.length;
            writer.writeValue(value.length);
        }
 
        assertArgumentCount(value.length, count, "coder array" + (this.localName? (" "+ this.localName): ""));
 
        let coders: Array<Coder> = [ ];
        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.readIndex();
 
            // 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.
            assert(count * WordSize <= reader.dataLength, "insufficient data length",
                "BUFFER_OVERRUN", { buffer: reader.bytes, offset: count * WordSize, length: reader.dataLength });
        }
        let coders: Array<Coder> = [];
        for (let i = 0; i < count; i++) { coders.push(new AnonymousCoder(this.coder)); }
 
        return unpack(reader, coders);
    }
}