utils.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. "use strict";
  2. /*
  3. This file is part of web3.js.
  4. web3.js is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU Lesser General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. web3.js is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General Public License
  13. along with web3.js. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. Object.defineProperty(exports, "__esModule", { value: true });
  16. exports.hexToUint8Array = exports.uint8ArrayToHexString = exports.padLeft = exports.numberToHex = exports.hexToNumber = exports.codePointToInt = exports.transformJsonDataToAbiFormat = exports.fetchArrayElement = exports.ethAbiToJsonSchema = exports.abiSchemaToJsonSchema = exports.parseBaseType = void 0;
  17. const web3_errors_1 = require("web3-errors");
  18. const constants_js_1 = require("./constants.js");
  19. const abi_js_1 = require("./validation/abi.js");
  20. const string_js_1 = require("./validation/string.js");
  21. const errors_js_1 = require("./errors.js");
  22. const extraTypes = ['hex', 'number', 'blockNumber', 'blockNumberOrTag', 'filter', 'bloom'];
  23. const parseBaseType = (type) => {
  24. // Remove all empty spaces to avoid any parsing issue.
  25. let strippedType = type.replace(/ /, '');
  26. let baseTypeSize;
  27. let isArray = false;
  28. let arraySizes = [];
  29. if (type.includes('[')) {
  30. // Extract the array type
  31. strippedType = strippedType.slice(0, strippedType.indexOf('['));
  32. // Extract array indexes
  33. arraySizes = [...type.matchAll(/(?:\[(\d*)\])/g)]
  34. .map(match => parseInt(match[1], 10))
  35. .map(size => (Number.isNaN(size) ? -1 : size));
  36. isArray = arraySizes.length > 0;
  37. }
  38. if (constants_js_1.VALID_ETH_BASE_TYPES.includes(strippedType)) {
  39. return { baseType: strippedType, isArray, baseTypeSize, arraySizes };
  40. }
  41. if (strippedType.startsWith('int')) {
  42. baseTypeSize = parseInt(strippedType.substring(3), 10);
  43. strippedType = 'int';
  44. }
  45. else if (strippedType.startsWith('uint')) {
  46. baseTypeSize = parseInt(type.substring(4), 10);
  47. strippedType = 'uint';
  48. }
  49. else if (strippedType.startsWith('bytes')) {
  50. baseTypeSize = parseInt(strippedType.substring(5), 10);
  51. strippedType = 'bytes';
  52. }
  53. else {
  54. return { baseType: undefined, isArray: false, baseTypeSize: undefined, arraySizes };
  55. }
  56. return { baseType: strippedType, isArray, baseTypeSize, arraySizes };
  57. };
  58. exports.parseBaseType = parseBaseType;
  59. const convertEthType = (type, parentSchema = {}) => {
  60. const typePropertyPresent = Object.keys(parentSchema).includes('type');
  61. if (typePropertyPresent) {
  62. throw new errors_js_1.Web3ValidatorError([
  63. {
  64. keyword: 'eth',
  65. message: 'Either "eth" or "type" can be presented in schema',
  66. params: { eth: type },
  67. instancePath: '',
  68. schemaPath: '',
  69. },
  70. ]);
  71. }
  72. const { baseType, baseTypeSize } = (0, exports.parseBaseType)(type);
  73. if (!baseType && !extraTypes.includes(type)) {
  74. throw new errors_js_1.Web3ValidatorError([
  75. {
  76. keyword: 'eth',
  77. message: `Eth data type "${type}" is not valid`,
  78. params: { eth: type },
  79. instancePath: '',
  80. schemaPath: '',
  81. },
  82. ]);
  83. }
  84. if (baseType) {
  85. if (baseType === 'tuple') {
  86. throw new Error('"tuple" type is not implemented directly.');
  87. }
  88. return { format: `${baseType}${baseTypeSize !== null && baseTypeSize !== void 0 ? baseTypeSize : ''}`, required: true };
  89. }
  90. if (type) {
  91. return { format: type, required: true };
  92. }
  93. return {};
  94. };
  95. const abiSchemaToJsonSchema = (abis, level = '/0') => {
  96. const schema = {
  97. type: 'array',
  98. items: [],
  99. maxItems: abis.length,
  100. minItems: abis.length,
  101. };
  102. for (const [index, abi] of abis.entries()) {
  103. // eslint-disable-next-line no-nested-ternary
  104. let abiType;
  105. let abiName;
  106. let abiComponents = [];
  107. // If it's a complete Abi Parameter
  108. // e.g. {name: 'a', type: 'uint'}
  109. if ((0, abi_js_1.isAbiParameterSchema)(abi)) {
  110. abiType = abi.type;
  111. abiName = abi.name;
  112. abiComponents = abi.components;
  113. // If its short form string value e.g. ['uint']
  114. }
  115. else if (typeof abi === 'string') {
  116. abiType = abi;
  117. abiName = `${level}/${index}`;
  118. // If it's provided in short form of tuple e.g. [['uint', 'string']]
  119. }
  120. else if (Array.isArray(abi)) {
  121. // If its custom tuple e.g. ['tuple[2]', ['uint', 'string']]
  122. if (abi[0] &&
  123. typeof abi[0] === 'string' &&
  124. abi[0].startsWith('tuple') &&
  125. !Array.isArray(abi[0]) &&
  126. abi[1] &&
  127. Array.isArray(abi[1])) {
  128. // eslint-disable-next-line prefer-destructuring
  129. abiType = abi[0];
  130. abiName = `${level}/${index}`;
  131. abiComponents = abi[1];
  132. }
  133. else {
  134. abiType = 'tuple';
  135. abiName = `${level}/${index}`;
  136. abiComponents = abi;
  137. }
  138. }
  139. const { baseType, isArray, arraySizes } = (0, exports.parseBaseType)(abiType);
  140. let childSchema;
  141. let lastSchema = schema;
  142. for (let i = arraySizes.length - 1; i > 0; i -= 1) {
  143. childSchema = {
  144. type: 'array',
  145. items: [],
  146. maxItems: arraySizes[i],
  147. minItems: arraySizes[i],
  148. };
  149. if (arraySizes[i] < 0) {
  150. delete childSchema.maxItems;
  151. delete childSchema.minItems;
  152. }
  153. lastSchema.items = childSchema;
  154. lastSchema = childSchema;
  155. }
  156. if (baseType === 'tuple' && !isArray) {
  157. const nestedTuple = (0, exports.abiSchemaToJsonSchema)(abiComponents, abiName);
  158. nestedTuple.$id = abiName;
  159. lastSchema.items.push(nestedTuple);
  160. }
  161. else if (baseType === 'tuple' && isArray) {
  162. const arraySize = arraySizes[0];
  163. const item = {
  164. $id: abiName,
  165. type: 'array',
  166. items: (0, exports.abiSchemaToJsonSchema)(abiComponents, abiName),
  167. maxItems: arraySize,
  168. minItems: arraySize,
  169. };
  170. if (arraySize < 0) {
  171. delete item.maxItems;
  172. delete item.minItems;
  173. }
  174. lastSchema.items.push(item);
  175. }
  176. else if (isArray) {
  177. const arraySize = arraySizes[0];
  178. const item = {
  179. type: 'array',
  180. $id: abiName,
  181. items: convertEthType(String(baseType)),
  182. minItems: arraySize,
  183. maxItems: arraySize,
  184. };
  185. if (arraySize < 0) {
  186. delete item.maxItems;
  187. delete item.minItems;
  188. }
  189. lastSchema.items.push(item);
  190. }
  191. else if (Array.isArray(lastSchema.items)) {
  192. // Array of non-tuple items
  193. lastSchema.items.push(Object.assign({ $id: abiName }, convertEthType(abiType)));
  194. }
  195. else {
  196. // Nested object
  197. lastSchema.items.items.push(Object.assign({ $id: abiName }, convertEthType(abiType)));
  198. }
  199. }
  200. return schema;
  201. };
  202. exports.abiSchemaToJsonSchema = abiSchemaToJsonSchema;
  203. const ethAbiToJsonSchema = (abis) => (0, exports.abiSchemaToJsonSchema)(abis);
  204. exports.ethAbiToJsonSchema = ethAbiToJsonSchema;
  205. const fetchArrayElement = (data, level) => {
  206. if (level === 1) {
  207. return data;
  208. }
  209. return (0, exports.fetchArrayElement)(data[0], level - 1);
  210. };
  211. exports.fetchArrayElement = fetchArrayElement;
  212. const transformJsonDataToAbiFormat = (abis, data, transformedData) => {
  213. const newData = [];
  214. for (const [index, abi] of abis.entries()) {
  215. // eslint-disable-next-line no-nested-ternary
  216. let abiType;
  217. let abiName;
  218. let abiComponents = [];
  219. // If it's a complete Abi Parameter
  220. // e.g. {name: 'a', type: 'uint'}
  221. if ((0, abi_js_1.isAbiParameterSchema)(abi)) {
  222. abiType = abi.type;
  223. abiName = abi.name;
  224. abiComponents = abi.components;
  225. // If its short form string value e.g. ['uint']
  226. }
  227. else if (typeof abi === 'string') {
  228. abiType = abi;
  229. // If it's provided in short form of tuple e.g. [['uint', 'string']]
  230. }
  231. else if (Array.isArray(abi)) {
  232. // If its custom tuple e.g. ['tuple[2]', ['uint', 'string']]
  233. if (abi[1] && Array.isArray(abi[1])) {
  234. abiType = abi[0];
  235. abiComponents = abi[1];
  236. }
  237. else {
  238. abiType = 'tuple';
  239. abiComponents = abi;
  240. }
  241. }
  242. const { baseType, isArray, arraySizes } = (0, exports.parseBaseType)(abiType);
  243. const dataItem = Array.isArray(data)
  244. ? data[index]
  245. : data[abiName];
  246. if (baseType === 'tuple' && !isArray) {
  247. newData.push((0, exports.transformJsonDataToAbiFormat)(abiComponents, dataItem, transformedData));
  248. }
  249. else if (baseType === 'tuple' && isArray) {
  250. const tupleData = [];
  251. for (const tupleItem of dataItem) {
  252. // Nested array
  253. if (arraySizes.length > 1) {
  254. const nestedItems = (0, exports.fetchArrayElement)(tupleItem, arraySizes.length - 1);
  255. const nestedData = [];
  256. for (const nestedItem of nestedItems) {
  257. nestedData.push((0, exports.transformJsonDataToAbiFormat)(abiComponents, nestedItem, transformedData));
  258. }
  259. tupleData.push(nestedData);
  260. }
  261. else {
  262. tupleData.push((0, exports.transformJsonDataToAbiFormat)(abiComponents, tupleItem, transformedData));
  263. }
  264. }
  265. newData.push(tupleData);
  266. }
  267. else {
  268. newData.push(dataItem);
  269. }
  270. }
  271. // Have to reassign before pushing to transformedData
  272. // eslint-disable-next-line no-param-reassign
  273. transformedData = transformedData !== null && transformedData !== void 0 ? transformedData : [];
  274. transformedData.push(...newData);
  275. return transformedData;
  276. };
  277. exports.transformJsonDataToAbiFormat = transformJsonDataToAbiFormat;
  278. /**
  279. * Code points to int
  280. */
  281. const codePointToInt = (codePoint) => {
  282. if (codePoint >= 48 && codePoint <= 57) {
  283. /* ['0'..'9'] -> [0..9] */
  284. return codePoint - 48;
  285. }
  286. if (codePoint >= 65 && codePoint <= 70) {
  287. /* ['A'..'F'] -> [10..15] */
  288. return codePoint - 55;
  289. }
  290. if (codePoint >= 97 && codePoint <= 102) {
  291. /* ['a'..'f'] -> [10..15] */
  292. return codePoint - 87;
  293. }
  294. throw new Error(`Invalid code point: ${codePoint}`);
  295. };
  296. exports.codePointToInt = codePointToInt;
  297. /**
  298. * Converts value to it's number representation
  299. */
  300. const hexToNumber = (value) => {
  301. if (!(0, string_js_1.isHexStrict)(value)) {
  302. throw new Error('Invalid hex string');
  303. }
  304. const [negative, hexValue] = value.startsWith('-') ? [true, value.slice(1)] : [false, value];
  305. const num = BigInt(hexValue);
  306. if (num > Number.MAX_SAFE_INTEGER) {
  307. return negative ? -num : num;
  308. }
  309. if (num < Number.MIN_SAFE_INTEGER) {
  310. return num;
  311. }
  312. return negative ? -1 * Number(num) : Number(num);
  313. };
  314. exports.hexToNumber = hexToNumber;
  315. /**
  316. * Converts value to it's hex representation
  317. */
  318. const numberToHex = (value) => {
  319. if ((typeof value === 'number' || typeof value === 'bigint') && value < 0) {
  320. return `-0x${value.toString(16).slice(1)}`;
  321. }
  322. if ((typeof value === 'number' || typeof value === 'bigint') && value >= 0) {
  323. return `0x${value.toString(16)}`;
  324. }
  325. if (typeof value === 'string' && (0, string_js_1.isHexStrict)(value)) {
  326. const [negative, hex] = value.startsWith('-') ? [true, value.slice(1)] : [false, value];
  327. const hexValue = hex.split(/^(-)?0(x|X)/).slice(-1)[0];
  328. return `${negative ? '-' : ''}0x${hexValue.replace(/^0+/, '').toLowerCase()}`;
  329. }
  330. if (typeof value === 'string' && !(0, string_js_1.isHexStrict)(value)) {
  331. return (0, exports.numberToHex)(BigInt(value));
  332. }
  333. throw new web3_errors_1.InvalidNumberError(value);
  334. };
  335. exports.numberToHex = numberToHex;
  336. /**
  337. * Adds a padding on the left of a string, if value is a integer or bigInt will be converted to a hex string.
  338. */
  339. const padLeft = (value, characterAmount, sign = '0') => {
  340. if (typeof value === 'string' && !(0, string_js_1.isHexStrict)(value)) {
  341. return value.padStart(characterAmount, sign);
  342. }
  343. const hex = typeof value === 'string' && (0, string_js_1.isHexStrict)(value) ? value : (0, exports.numberToHex)(value);
  344. const [prefix, hexValue] = hex.startsWith('-') ? ['-0x', hex.slice(3)] : ['0x', hex.slice(2)];
  345. return `${prefix}${hexValue.padStart(characterAmount, sign)}`;
  346. };
  347. exports.padLeft = padLeft;
  348. function uint8ArrayToHexString(uint8Array) {
  349. let hexString = '0x';
  350. for (const e of uint8Array) {
  351. const hex = e.toString(16);
  352. hexString += hex.length === 1 ? `0${hex}` : hex;
  353. }
  354. return hexString;
  355. }
  356. exports.uint8ArrayToHexString = uint8ArrayToHexString;
  357. function hexToUint8Array(hex) {
  358. let value;
  359. if (hex.toLowerCase().startsWith('0x')) {
  360. value = hex.slice(2);
  361. }
  362. else {
  363. value = hex;
  364. }
  365. if (value.length % 2 !== 0) {
  366. throw new web3_errors_1.InvalidBytesError(`hex string has odd length: ${hex}`);
  367. }
  368. const bytes = new Uint8Array(Math.ceil(value.length / 2));
  369. for (let i = 0; i < bytes.length; i += 1) {
  370. const byte = parseInt(value.substring(i * 2, i * 2 + 2), 16);
  371. bytes[i] = byte;
  372. }
  373. return bytes;
  374. }
  375. exports.hexToUint8Array = hexToUint8Array;
  376. //# sourceMappingURL=utils.js.map