github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/js/src/convert.ts (about)

     1  // The convert functions are based on types used by ethers AbiCoder but we redefine some types here to keep our
     2  // functional dependency on those types minimal
     3  
     4  // Same as ethers hybrid array/record type used for dynamic returns
     5  export type Result<T = any> = readonly T[] & { readonly [key: string]: T };
     6  
     7  // Bash-in-place version of Result
     8  type Presult<T = any> = T[] & { [key: string]: T };
     9  
    10  // Minimal restriction of ethers ParamType
    11  type ParamType = {
    12    readonly name: string;
    13    readonly type: string;
    14  };
    15  
    16  // Minimal restrictions of ethers BigNumber
    17  type BigNumber = {
    18    toNumber(): number;
    19  };
    20  
    21  const bytesNN = /bytes([0-9]+)/;
    22  const zeroAddress = '0x0000000000000000000000000000000000000000';
    23  
    24  // Converts values from those returned by Burrow's GRPC bindings to those expected by ABI encoder
    25  export function preEncodeResult(args: Result, inputs: ParamType[]): Result {
    26    const out: Presult = [];
    27    checkParamTypesAndArgs('burrowToAbi', inputs, args);
    28    for (let i = 0; i < inputs.length; i++) {
    29      pushValue(out, preEncode(args[i], inputs[i].type), inputs[i]);
    30    }
    31    return Object.freeze(out);
    32  }
    33  
    34  function preEncode(arg: unknown, type: string): unknown {
    35    if (/address/.test(type)) {
    36      return recApply(
    37        (input) => (input === '0x0' || !input ? zeroAddress : prefixedHexString(input)),
    38        arg as NestedArray<string>,
    39      );
    40    }
    41    const match = bytesNN.exec(type);
    42    if (match) {
    43      // Handle bytes32 differently - for legacy reasons they are used as identifiers and represented as hex strings
    44      return recApply((input) => {
    45        return padBytes(input, Number(match[1]));
    46      }, arg as NestedArray<string>);
    47    }
    48    if (/bytes/.test(type)) {
    49      return recApply(toBuffer, arg as NestedArray<string>);
    50    }
    51    return arg;
    52  }
    53  
    54  // Converts values from those returned by ABI decoder to those expected by Burrow's GRPC bindings
    55  export function postDecodeResult(args: Result, outputs: ParamType[] | undefined): Result {
    56    const out: Presult = [];
    57    if (!outputs) {
    58      return Object.freeze(out);
    59    }
    60    checkParamTypesAndArgs('abiToBurrow', outputs, args);
    61    for (let i = 0; i < outputs.length; i++) {
    62      pushValue(out, postDecode(args[i], outputs[i].type), outputs[i]);
    63    }
    64    return Object.freeze(out);
    65  }
    66  
    67  function postDecode(arg: unknown, type: string): unknown {
    68    if (/address/.test(type)) {
    69      return recApply(unprefixedHexString, arg as NestedArray<string>);
    70    }
    71    if (/bytes/.test(type)) {
    72      return recApply(toBuffer, arg as NestedArray<string>);
    73    }
    74    if (/int/.test(type)) {
    75      return recApply(numberToBurrow, arg as NestedArray<BigNumber>);
    76    }
    77    return arg;
    78  }
    79  
    80  function numberToBurrow(arg: BigNumber): BigNumber | number {
    81    try {
    82      // number is limited to 53 bits, BN will throw Error
    83      return arg.toNumber();
    84    } catch {
    85      // arg does not fit into number type, so keep it as BN
    86      return arg;
    87    }
    88  }
    89  
    90  export function withoutArrayElements(result: Result): Record<string, unknown> {
    91    return Object.fromEntries(Object.entries(result).filter(([k]) => isNaN(Number(k))));
    92  }
    93  
    94  export function unprefixedHexString(arg: string | Uint8Array): string {
    95    if (arg instanceof Uint8Array) {
    96      return Buffer.from(arg).toString('hex').toUpperCase();
    97    }
    98    if (/^0x/i.test(arg)) {
    99      return arg.slice(2).toUpperCase();
   100    }
   101    return arg.toUpperCase();
   102  }
   103  
   104  // Adds a 0x prefix to a hex string if it doesn't have one already or returns a prefixed hex string from bytes
   105  export function prefixedHexString(arg?: string | Uint8Array): string {
   106    if (!arg) {
   107      return '';
   108    }
   109    if (typeof arg === 'string' && !/^0x/i.test(arg)) {
   110      return '0x' + arg.toLowerCase();
   111    }
   112    if (arg instanceof Uint8Array) {
   113      return '0x' + Buffer.from(arg).toString('hex').toLowerCase();
   114    }
   115    return arg.toLowerCase();
   116  }
   117  
   118  // Returns bytes buffer from hex string which may or may not have an 0x prefix
   119  export function toBuffer(arg: string | Uint8Array): Buffer {
   120    if (arg instanceof Uint8Array) {
   121      return Buffer.from(arg);
   122    }
   123    if (/^0x/i.test(arg)) {
   124      arg = arg.slice(2);
   125    }
   126    return Buffer.from(arg, 'hex');
   127  }
   128  
   129  type NestedArray<T> = T | NestedArray<T>[];
   130  
   131  // Recursively applies func to an arbitrarily nested array with a single element type as described by solidity tuples
   132  function recApply<A, B>(func: (input: A) => B, args: NestedArray<A>): NestedArray<B> {
   133    return Array.isArray(args) ? args.map((arg) => recApply(func, arg)) : func(args);
   134  }
   135  
   136  function checkParamTypesAndArgs(functionName: string, types: ParamType[], args: ArrayLike<unknown>) {
   137    if (types.length !== args.length) {
   138      const quantifier = types.length > args.length ? 'more' : 'fewer';
   139      const typesString = types.map((t) => t.name).join(', ');
   140      const argsString = Object.values(args)
   141        .map((a) => JSON.stringify(a))
   142        .join(', ');
   143      throw new Error(
   144        `${functionName} received ${quantifier} types than arguments: types: [${typesString}], args: [${argsString}]`,
   145      );
   146    }
   147  }
   148  
   149  function pushValue(out: Presult, value: unknown, type: ParamType): void {
   150    out.push(value);
   151    if (type.name) {
   152      out[type.name] = value;
   153    }
   154  }
   155  
   156  export function padBytes(buf: Uint8Array | string, n: number): Buffer {
   157    if (typeof buf === 'string') {
   158      // Parse hex (possible 0x prefixed) into bytes!
   159      buf = toBuffer(buf);
   160    }
   161    if (buf.length > n) {
   162      throw new Error(`cannot pad buffer ${buf} of length ${buf.length} to ${n} because it is longer than ${n}`);
   163    }
   164    const padded = Buffer.alloc(n);
   165    Buffer.from(buf).copy(padded);
   166    return padded;
   167  }