import { Format } from "@truffle/codec"; import { BN } from "bn.js"; import { Big } from "big.js"; export function safeNativizeVariables(variables: { [name: string]: Format.Values.Result; }): { [name: string]: any } { return Object.assign( {}, ...Object.entries(variables).map(([name, value]) => { try { return { [name]: safeNativize(value) }; } catch (_) { return undefined; //I guess?? } }) ); } //HACK! Avoid using! /** * WARNING! Do NOT use this function in real code unless you absolutely have * to! Using it in controlled tests is fine, but do NOT use it in real code if * you have any better option! * * This function is a giant hack. It will throw exceptions on numbers that * don't fit in a Javascript number. It loses various information. It was * only ever written to support our hacked-together watch expression system, * and later repurposed to make testing easier. * * If you are not doing something as horrible as evaluating user-inputted * Javascript expressions meant to operate upon Solidity variables, then you * probably have a better option than using this in real code! * * (For instance, if you just want to nicely print individual values, without * attempting to first operate on them via Javascript expressions, we have the * [[ResultInspector]] class, which can be used with Node's * [util.inspect()](https://nodejs.org/api/util.html#util_util_inspect_object_options) * to do exactly that.) * * Remember, the decoder output format was made to be machine-readable. It * shouldn't be too hard for you to process. If it comes to it, copy-paste * this code and dehackify it for your use case, which hopefully is more * manageable than the one that caused us to write this. */ export function safeNativize(result: Format.Values.Result): any { return safeNativizeWithTable(result, []); } function safeNativizeWithTable( result: Format.Values.Result, seenSoFar: any[] ): any { if (result.kind === "error") { switch (result.error.kind) { case "BoolOutOfRangeError": return true; default: return undefined; } } //NOTE: for simplicity, only arrays & structs will call unsafeNativizeWithTable; //other containers will just call unsafeNativize because they can get away with it //(only things that can *be* circular need unsafeNativizeWithTable, not things that //can merely *contain* circularities) switch (result.type.typeClass) { case "uint": case "int": const bnVal = (( result )).value.asBN; // change bnVal's prototype to BN //@ts-ignore bnVal.__proto__ = BN.prototype; try { return bnVal.toNumber(); //WARNING } catch (_) { return bnVal.toString(); } case "bool": return (result).value.asBoolean; case "bytes": return (result).value.asHex; case "address": return (result).value.asAddress; case "string": { let coercedResult = result; switch (coercedResult.value.kind) { case "valid": return coercedResult.value.asString; case "malformed": // this will turn malformed utf-8 into replacement characters (U+FFFD) (WARNING) // note we need to cut off the 0x prefix return Buffer.from( coercedResult.value.asHex.slice(2), "hex" ).toString(); } } case "fixed": case "ufixed": //HACK: Big doesn't have a toNumber() method, so we convert to string and then parse with Number //NOTE: we don't bother setting the magic variables Big.NE or Big.PE first, as the choice of //notation shouldn't affect the result (can you believe I have to write this? @_@) const val = (( result )).value.asBig; // change val's prototype to Big //@ts-ignore val.__proto__ = Big.prototype; return Number(val.toString()); //WARNING case "array": { let coercedResult = result; if (coercedResult.reference === undefined) { //we need to do some pointer stuff here, so let's first create our new //object we'll be pointing to //[we don't want to alter the original accidentally so let's clone a bit] let output: any[] = [...coercedResult.value]; //now, we can't use a map here, or we'll screw things up! //we want to *mutate* output, not replace it with a new object for (let index = 0; index < output.length; index++) { output[index] = safeNativizeWithTable(output[index], [ output, ...seenSoFar ]); } return output; } else { return seenSoFar[coercedResult.reference - 1]; } } case "userDefinedValueType": { return safeNativize( (result).value ); } case "mapping": return Object.assign( {}, ...(result).value.map(({ key, value }) => ({ [safeNativize(key).toString()]: safeNativize(value) })) ); case "struct": { let coercedResult = result; if (coercedResult.reference === undefined) { //we need to do some pointer stuff here, so let's first create our new //object we'll be pointing to let output = Object.assign( {}, ...(result).value.map( ({ name, value }) => ({ [name]: value //we *don't* nativize yet! }) ) ); //now, we can't use a map here, or we'll screw things up! //we want to *mutate* output, not replace it with a new object for (let name in output) { output[name] = safeNativizeWithTable(output[name], [ output, ...seenSoFar ]); } return output; } else { return seenSoFar[coercedResult.reference - 1]; } } case "type": switch (result.type.type.typeClass) { case "contract": return Object.assign( {}, ...(result).value.map( ({ name, value }) => ({ [name]: safeNativize(value) }) ) ); case "enum": return Object.assign( {}, ...(result).value.map(enumValue => ({ [enumValue.value.name]: safeNativize(enumValue) })) ); } case "tuple": return (result).value.map(({ value }) => safeNativize(value) ); case "magic": return Object.assign( {}, ...Object.entries((result).value).map( ([key, value]) => ({ [key]: safeNativize(value) }) ) ); case "enum": return enumFullName(result); case "contract": return (result).value.address; //we no longer include additional info case "function": switch (result.type.visibility) { case "external": { let coercedResult = result; switch (coercedResult.value.kind) { case "known": return `${coercedResult.value.contract.class.typeName}(${coercedResult.value.contract.address}).${coercedResult.value.abi.name}`; case "invalid": return `${coercedResult.value.contract.class.typeName}(${coercedResult.value.contract.address}).call(${coercedResult.value.selector}...)`; case "unknown": return `${coercedResult.value.contract.address}.call(${coercedResult.value.selector}...)`; } } case "internal": { let coercedResult = result; switch (coercedResult.value.kind) { case "function": if (coercedResult.value.definedIn) { return `${coercedResult.value.definedIn.typeName}.${coercedResult.value.name}`; } else { return coercedResult.value.name; } case "exception": return coercedResult.value.deployedProgramCounter === 0 ? `` : ``; case "unknown": return ``; } } } } } function enumFullName(value: Format.Values.EnumValue): string { switch (value.type.kind) { case "local": return `${value.type.definingContractName}.${value.type.typeName}.${value.value.name}`; case "global": return `${value.type.typeName}.${value.value.name}`; } }