data.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. /**
  2. * Some data helpers.
  3. *
  4. *
  5. * @_subsection api/utils:Data Helpers [about-data]
  6. */
  7. import { assert, assertArgument } from "./errors.js";
  8. /**
  9. * A [[HexString]] whose length is even, which ensures it is a valid
  10. * representation of binary data.
  11. */
  12. export type DataHexString = string;
  13. /**
  14. * A string which is prefixed with ``0x`` and followed by any number
  15. * of case-agnostic hexadecimal characters.
  16. *
  17. * It must match the regular expression ``/0x[0-9A-Fa-f]*\/``.
  18. */
  19. export type HexString = string;
  20. /**
  21. * An object that can be used to represent binary data.
  22. */
  23. export type BytesLike = DataHexString | Uint8Array;
  24. function _getBytes(value: BytesLike, name?: string, copy?: boolean): Uint8Array {
  25. if (value instanceof Uint8Array) {
  26. if (copy) { return new Uint8Array(value); }
  27. return value;
  28. }
  29. if (typeof(value) === "string" && value.match(/^0x(?:[0-9a-f][0-9a-f])*$/i)) {
  30. const result = new Uint8Array((value.length - 2) / 2);
  31. let offset = 2;
  32. for (let i = 0; i < result.length; i++) {
  33. result[i] = parseInt(value.substring(offset, offset + 2), 16);
  34. offset += 2;
  35. }
  36. return result;
  37. }
  38. assertArgument(false, "invalid BytesLike value", name || "value", value);
  39. }
  40. /**
  41. * Get a typed Uint8Array for %%value%%. If already a Uint8Array
  42. * the original %%value%% is returned; if a copy is required use
  43. * [[getBytesCopy]].
  44. *
  45. * @see: getBytesCopy
  46. */
  47. export function getBytes(value: BytesLike, name?: string): Uint8Array {
  48. return _getBytes(value, name, false);
  49. }
  50. /**
  51. * Get a typed Uint8Array for %%value%%, creating a copy if necessary
  52. * to prevent any modifications of the returned value from being
  53. * reflected elsewhere.
  54. *
  55. * @see: getBytes
  56. */
  57. export function getBytesCopy(value: BytesLike, name?: string): Uint8Array {
  58. return _getBytes(value, name, true);
  59. }
  60. /**
  61. * Returns true if %%value%% is a valid [[HexString]].
  62. *
  63. * If %%length%% is ``true`` or a //number//, it also checks that
  64. * %%value%% is a valid [[DataHexString]] of %%length%% (if a //number//)
  65. * bytes of data (e.g. ``0x1234`` is 2 bytes).
  66. */
  67. export function isHexString(value: any, length?: number | boolean): value is `0x${ string }` {
  68. if (typeof(value) !== "string" || !value.match(/^0x[0-9A-Fa-f]*$/)) {
  69. return false
  70. }
  71. if (typeof(length) === "number" && value.length !== 2 + 2 * length) { return false; }
  72. if (length === true && (value.length % 2) !== 0) { return false; }
  73. return true;
  74. }
  75. /**
  76. * Returns true if %%value%% is a valid representation of arbitrary
  77. * data (i.e. a valid [[DataHexString]] or a Uint8Array).
  78. */
  79. export function isBytesLike(value: any): value is BytesLike {
  80. return (isHexString(value, true) || (value instanceof Uint8Array));
  81. }
  82. const HexCharacters: string = "0123456789abcdef";
  83. /**
  84. * Returns a [[DataHexString]] representation of %%data%%.
  85. */
  86. export function hexlify(data: BytesLike): string {
  87. const bytes = getBytes(data);
  88. let result = "0x";
  89. for (let i = 0; i < bytes.length; i++) {
  90. const v = bytes[i];
  91. result += HexCharacters[(v & 0xf0) >> 4] + HexCharacters[v & 0x0f];
  92. }
  93. return result;
  94. }
  95. /**
  96. * Returns a [[DataHexString]] by concatenating all values
  97. * within %%data%%.
  98. */
  99. export function concat(datas: ReadonlyArray<BytesLike>): string {
  100. return "0x" + datas.map((d) => hexlify(d).substring(2)).join("");
  101. }
  102. /**
  103. * Returns the length of %%data%%, in bytes.
  104. */
  105. export function dataLength(data: BytesLike): number {
  106. if (isHexString(data, true)) { return (data.length - 2) / 2; }
  107. return getBytes(data).length;
  108. }
  109. /**
  110. * Returns a [[DataHexString]] by slicing %%data%% from the %%start%%
  111. * offset to the %%end%% offset.
  112. *
  113. * By default %%start%% is 0 and %%end%% is the length of %%data%%.
  114. */
  115. export function dataSlice(data: BytesLike, start?: number, end?: number): string {
  116. const bytes = getBytes(data);
  117. if (end != null && end > bytes.length) {
  118. assert(false, "cannot slice beyond data bounds", "BUFFER_OVERRUN", {
  119. buffer: bytes, length: bytes.length, offset: end
  120. });
  121. }
  122. return hexlify(bytes.slice((start == null) ? 0: start, (end == null) ? bytes.length: end));
  123. }
  124. /**
  125. * Return the [[DataHexString]] result by stripping all **leading**
  126. ** zero bytes from %%data%%.
  127. */
  128. export function stripZerosLeft(data: BytesLike): string {
  129. let bytes = hexlify(data).substring(2);
  130. while (bytes.startsWith("00")) { bytes = bytes.substring(2); }
  131. return "0x" + bytes;
  132. }
  133. function zeroPad(data: BytesLike, length: number, left: boolean): string {
  134. const bytes = getBytes(data);
  135. assert(length >= bytes.length, "padding exceeds data length", "BUFFER_OVERRUN", {
  136. buffer: new Uint8Array(bytes),
  137. length: length,
  138. offset: length + 1
  139. });
  140. const result = new Uint8Array(length);
  141. result.fill(0);
  142. if (left) {
  143. result.set(bytes, length - bytes.length);
  144. } else {
  145. result.set(bytes, 0);
  146. }
  147. return hexlify(result);
  148. }
  149. /**
  150. * Return the [[DataHexString]] of %%data%% padded on the **left**
  151. * to %%length%% bytes.
  152. *
  153. * If %%data%% already exceeds %%length%%, a [[BufferOverrunError]] is
  154. * thrown.
  155. *
  156. * This pads data the same as **values** are in Solidity
  157. * (e.g. ``uint128``).
  158. */
  159. export function zeroPadValue(data: BytesLike, length: number): string {
  160. return zeroPad(data, length, true);
  161. }
  162. /**
  163. * Return the [[DataHexString]] of %%data%% padded on the **right**
  164. * to %%length%% bytes.
  165. *
  166. * If %%data%% already exceeds %%length%%, a [[BufferOverrunError]] is
  167. * thrown.
  168. *
  169. * This pads data the same as **bytes** are in Solidity
  170. * (e.g. ``bytes16``).
  171. */
  172. export function zeroPadBytes(data: BytesLike, length: number): string {
  173. return zeroPad(data, length, false);
  174. }