index.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. "use strict";
  2. let _permanentCensorErrors = false;
  3. let _censorErrors = false;
  4. const LogLevels: { [ name: string ]: number } = { debug: 1, "default": 2, info: 2, warning: 3, error: 4, off: 5 };
  5. let _logLevel = LogLevels["default"];
  6. import { version } from "./_version";
  7. let _globalLogger: Logger = null;
  8. function _checkNormalize(): string {
  9. try {
  10. const missing: Array<string> = [ ];
  11. // Make sure all forms of normalization are supported
  12. ["NFD", "NFC", "NFKD", "NFKC"].forEach((form) => {
  13. try {
  14. if ("test".normalize(form) !== "test") {
  15. throw new Error("bad normalize");
  16. };
  17. } catch(error) {
  18. missing.push(form);
  19. }
  20. });
  21. if (missing.length) {
  22. throw new Error("missing " + missing.join(", "));
  23. }
  24. if (String.fromCharCode(0xe9).normalize("NFD") !== String.fromCharCode(0x65, 0x0301)) {
  25. throw new Error("broken implementation")
  26. }
  27. } catch (error) {
  28. return error.message;
  29. }
  30. return null;
  31. }
  32. const _normalizeError = _checkNormalize();
  33. export enum LogLevel {
  34. DEBUG = "DEBUG",
  35. INFO = "INFO",
  36. WARNING = "WARNING",
  37. ERROR = "ERROR",
  38. OFF = "OFF"
  39. }
  40. export enum ErrorCode {
  41. ///////////////////
  42. // Generic Errors
  43. // Unknown Error
  44. UNKNOWN_ERROR = "UNKNOWN_ERROR",
  45. // Not Implemented
  46. NOT_IMPLEMENTED = "NOT_IMPLEMENTED",
  47. // Unsupported Operation
  48. // - operation
  49. UNSUPPORTED_OPERATION = "UNSUPPORTED_OPERATION",
  50. // Network Error (i.e. Ethereum Network, such as an invalid chain ID)
  51. // - event ("noNetwork" is not re-thrown in provider.ready; otherwise thrown)
  52. NETWORK_ERROR = "NETWORK_ERROR",
  53. // Some sort of bad response from the server
  54. SERVER_ERROR = "SERVER_ERROR",
  55. // Timeout
  56. TIMEOUT = "TIMEOUT",
  57. ///////////////////
  58. // Operational Errors
  59. // Buffer Overrun
  60. BUFFER_OVERRUN = "BUFFER_OVERRUN",
  61. // Numeric Fault
  62. // - operation: the operation being executed
  63. // - fault: the reason this faulted
  64. NUMERIC_FAULT = "NUMERIC_FAULT",
  65. ///////////////////
  66. // Argument Errors
  67. // Missing new operator to an object
  68. // - name: The name of the class
  69. MISSING_NEW = "MISSING_NEW",
  70. // Invalid argument (e.g. value is incompatible with type) to a function:
  71. // - argument: The argument name that was invalid
  72. // - value: The value of the argument
  73. INVALID_ARGUMENT = "INVALID_ARGUMENT",
  74. // Missing argument to a function:
  75. // - count: The number of arguments received
  76. // - expectedCount: The number of arguments expected
  77. MISSING_ARGUMENT = "MISSING_ARGUMENT",
  78. // Too many arguments
  79. // - count: The number of arguments received
  80. // - expectedCount: The number of arguments expected
  81. UNEXPECTED_ARGUMENT = "UNEXPECTED_ARGUMENT",
  82. ///////////////////
  83. // Blockchain Errors
  84. // Call exception
  85. // - transaction: the transaction
  86. // - address?: the contract address
  87. // - args?: The arguments passed into the function
  88. // - method?: The Solidity method signature
  89. // - errorSignature?: The EIP848 error signature
  90. // - errorArgs?: The EIP848 error parameters
  91. // - reason: The reason (only for EIP848 "Error(string)")
  92. CALL_EXCEPTION = "CALL_EXCEPTION",
  93. // Insufficient funds (< value + gasLimit * gasPrice)
  94. // - transaction: the transaction attempted
  95. INSUFFICIENT_FUNDS = "INSUFFICIENT_FUNDS",
  96. // Nonce has already been used
  97. // - transaction: the transaction attempted
  98. NONCE_EXPIRED = "NONCE_EXPIRED",
  99. // The replacement fee for the transaction is too low
  100. // - transaction: the transaction attempted
  101. REPLACEMENT_UNDERPRICED = "REPLACEMENT_UNDERPRICED",
  102. // The gas limit could not be estimated
  103. // - transaction: the transaction passed to estimateGas
  104. UNPREDICTABLE_GAS_LIMIT = "UNPREDICTABLE_GAS_LIMIT",
  105. // The transaction was replaced by one with a higher gas price
  106. // - reason: "cancelled", "replaced" or "repriced"
  107. // - cancelled: true if reason == "cancelled" or reason == "replaced")
  108. // - hash: original transaction hash
  109. // - replacement: the full TransactionsResponse for the replacement
  110. // - receipt: the receipt of the replacement
  111. TRANSACTION_REPLACED = "TRANSACTION_REPLACED",
  112. };
  113. const HEX = "0123456789abcdef";
  114. export class Logger {
  115. readonly version: string;
  116. static errors = ErrorCode;
  117. static levels = LogLevel;
  118. constructor(version: string) {
  119. Object.defineProperty(this, "version", {
  120. enumerable: true,
  121. value: version,
  122. writable: false
  123. });
  124. }
  125. _log(logLevel: LogLevel, args: Array<any>): void {
  126. const level = logLevel.toLowerCase();
  127. if (LogLevels[level] == null) {
  128. this.throwArgumentError("invalid log level name", "logLevel", logLevel);
  129. }
  130. if (_logLevel > LogLevels[level]) { return; }
  131. console.log.apply(console, args);
  132. }
  133. debug(...args: Array<any>): void {
  134. this._log(Logger.levels.DEBUG, args);
  135. }
  136. info(...args: Array<any>): void {
  137. this._log(Logger.levels.INFO, args);
  138. }
  139. warn(...args: Array<any>): void {
  140. this._log(Logger.levels.WARNING, args);
  141. }
  142. makeError(message: string, code?: ErrorCode, params?: any): Error {
  143. // Errors are being censored
  144. if (_censorErrors) {
  145. return this.makeError("censored error", code, { });
  146. }
  147. if (!code) { code = Logger.errors.UNKNOWN_ERROR; }
  148. if (!params) { params = {}; }
  149. const messageDetails: Array<string> = [];
  150. Object.keys(params).forEach((key) => {
  151. const value = params[key];
  152. try {
  153. if (value instanceof Uint8Array) {
  154. let hex = "";
  155. for (let i = 0; i < value.length; i++) {
  156. hex += HEX[value[i] >> 4];
  157. hex += HEX[value[i] & 0x0f];
  158. }
  159. messageDetails.push(key + "=Uint8Array(0x" + hex + ")");
  160. } else {
  161. messageDetails.push(key + "=" + JSON.stringify(value));
  162. }
  163. } catch (error) {
  164. messageDetails.push(key + "=" + JSON.stringify(params[key].toString()));
  165. }
  166. });
  167. messageDetails.push(`code=${ code }`);
  168. messageDetails.push(`version=${ this.version }`);
  169. const reason = message;
  170. let url = "";
  171. switch (code) {
  172. case ErrorCode.NUMERIC_FAULT: {
  173. url = "NUMERIC_FAULT";
  174. const fault = message;
  175. switch (fault) {
  176. case "overflow": case "underflow": case "division-by-zero":
  177. url += "-" + fault;
  178. break;
  179. case "negative-power": case "negative-width":
  180. url += "-unsupported";
  181. break;
  182. case "unbound-bitwise-result":
  183. url += "-unbound-result";
  184. break;
  185. }
  186. break;
  187. }
  188. case ErrorCode.CALL_EXCEPTION:
  189. case ErrorCode.INSUFFICIENT_FUNDS:
  190. case ErrorCode.MISSING_NEW:
  191. case ErrorCode.NONCE_EXPIRED:
  192. case ErrorCode.REPLACEMENT_UNDERPRICED:
  193. case ErrorCode.TRANSACTION_REPLACED:
  194. case ErrorCode.UNPREDICTABLE_GAS_LIMIT:
  195. url = code;
  196. break;
  197. }
  198. if (url) {
  199. message += " [ See: https:/\/links.ethers.org/v5-errors-" + url + " ]";
  200. }
  201. if (messageDetails.length) {
  202. message += " (" + messageDetails.join(", ") + ")";
  203. }
  204. // @TODO: Any??
  205. const error: any = new Error(message);
  206. error.reason = reason;
  207. error.code = code
  208. Object.keys(params).forEach(function(key) {
  209. error[key] = params[key];
  210. });
  211. return error;
  212. }
  213. throwError(message: string, code?: ErrorCode, params?: any): never {
  214. throw this.makeError(message, code, params);
  215. }
  216. throwArgumentError(message: string, name: string, value: any): never {
  217. return this.throwError(message, Logger.errors.INVALID_ARGUMENT, {
  218. argument: name,
  219. value: value
  220. });
  221. }
  222. assert(condition: any, message: string, code?: ErrorCode, params?: any): void {
  223. if (!!condition) { return; }
  224. this.throwError(message, code, params);
  225. }
  226. assertArgument(condition: any, message: string, name: string, value: any): void {
  227. if (!!condition) { return; }
  228. this.throwArgumentError(message, name, value);
  229. }
  230. checkNormalize(message?: string): void {
  231. if (message == null) { message = "platform missing String.prototype.normalize"; }
  232. if (_normalizeError) {
  233. this.throwError("platform missing String.prototype.normalize", Logger.errors.UNSUPPORTED_OPERATION, {
  234. operation: "String.prototype.normalize", form: _normalizeError
  235. });
  236. }
  237. }
  238. checkSafeUint53(value: number, message?: string): void {
  239. if (typeof(value) !== "number") { return; }
  240. if (message == null) { message = "value not safe"; }
  241. if (value < 0 || value >= 0x1fffffffffffff) {
  242. this.throwError(message, Logger.errors.NUMERIC_FAULT, {
  243. operation: "checkSafeInteger",
  244. fault: "out-of-safe-range",
  245. value: value
  246. });
  247. }
  248. if (value % 1) {
  249. this.throwError(message, Logger.errors.NUMERIC_FAULT, {
  250. operation: "checkSafeInteger",
  251. fault: "non-integer",
  252. value: value
  253. });
  254. }
  255. }
  256. checkArgumentCount(count: number, expectedCount: number, message?: string): void {
  257. if (message) {
  258. message = ": " + message;
  259. } else {
  260. message = "";
  261. }
  262. if (count < expectedCount) {
  263. this.throwError("missing argument" + message, Logger.errors.MISSING_ARGUMENT, {
  264. count: count,
  265. expectedCount: expectedCount
  266. });
  267. }
  268. if (count > expectedCount) {
  269. this.throwError("too many arguments" + message, Logger.errors.UNEXPECTED_ARGUMENT, {
  270. count: count,
  271. expectedCount: expectedCount
  272. });
  273. }
  274. }
  275. checkNew(target: any, kind: any): void {
  276. if (target === Object || target == null) {
  277. this.throwError("missing new", Logger.errors.MISSING_NEW, { name: kind.name });
  278. }
  279. }
  280. checkAbstract(target: any, kind: any): void {
  281. if (target === kind) {
  282. this.throwError(
  283. "cannot instantiate abstract class " + JSON.stringify(kind.name) + " directly; use a sub-class",
  284. Logger.errors.UNSUPPORTED_OPERATION,
  285. { name: target.name, operation: "new" }
  286. );
  287. } else if (target === Object || target == null) {
  288. this.throwError("missing new", Logger.errors.MISSING_NEW, { name: kind.name });
  289. }
  290. }
  291. static globalLogger(): Logger {
  292. if (!_globalLogger) { _globalLogger = new Logger(version); }
  293. return _globalLogger;
  294. }
  295. static setCensorship(censorship: boolean, permanent?: boolean): void {
  296. if (!censorship && permanent) {
  297. this.globalLogger().throwError("cannot permanently disable censorship", Logger.errors.UNSUPPORTED_OPERATION, {
  298. operation: "setCensorship"
  299. });
  300. }
  301. if (_permanentCensorErrors) {
  302. if (!censorship) { return; }
  303. this.globalLogger().throwError("error censorship permanent", Logger.errors.UNSUPPORTED_OPERATION, {
  304. operation: "setCensorship"
  305. });
  306. }
  307. _censorErrors = !!censorship;
  308. _permanentCensorErrors = !!permanent;
  309. }
  310. static setLogLevel(logLevel: LogLevel): void {
  311. const level = LogLevels[logLevel.toLowerCase()];
  312. if (level == null) {
  313. Logger.globalLogger().warn("invalid log level - " + logLevel);
  314. return;
  315. }
  316. _logLevel = level;
  317. }
  318. static from(version: string): Logger {
  319. return new Logger(version);
  320. }
  321. }