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 }