numbers/xdr_large_int.js

  1. /* eslint no-bitwise: ["error", {"allow": [">>"]}] */
  2. import { Hyper, UnsignedHyper } from '@stellar/js-xdr';
  3. import { Uint128 } from './uint128';
  4. import { Uint256 } from './uint256';
  5. import { Int128 } from './int128';
  6. import { Int256 } from './int256';
  7. import xdr from '../xdr';
  8. /**
  9. * A wrapper class to represent large XDR-encodable integers.
  10. *
  11. * This operates at a lower level than {@link ScInt} by forcing you to specify
  12. * the type / width / size in bits of the integer you're targeting, regardless
  13. * of the input value(s) you provide.
  14. *
  15. * @param {string} type - force a specific data type. the type choices are:
  16. * 'i64', 'u64', 'i128', 'u128', 'i256', and 'u256' (default: the smallest
  17. * one that fits the `value`) (see {@link XdrLargeInt.isType})
  18. * @param {number|bigint|string|Array<number|bigint|string>} values a list of
  19. * integer-like values interpreted in big-endian order
  20. */
  21. export class XdrLargeInt {
  22. /** @type {xdr.LargeInt} */
  23. int; // child class of a jsXdr.LargeInt
  24. /** @type {string} */
  25. type;
  26. constructor(type, values) {
  27. if (!(values instanceof Array)) {
  28. values = [values];
  29. }
  30. // normalize values to one type
  31. values = values.map((i) => {
  32. // micro-optimization to no-op on the likeliest input value:
  33. if (typeof i === 'bigint') {
  34. return i;
  35. }
  36. if (i instanceof XdrLargeInt) {
  37. return i.toBigInt();
  38. }
  39. return BigInt(i);
  40. });
  41. switch (type) {
  42. case 'i64':
  43. this.int = new Hyper(values);
  44. break;
  45. case 'i128':
  46. this.int = new Int128(values);
  47. break;
  48. case 'i256':
  49. this.int = new Int256(values);
  50. break;
  51. case 'u64':
  52. this.int = new UnsignedHyper(values);
  53. break;
  54. case 'u128':
  55. this.int = new Uint128(values);
  56. break;
  57. case 'u256':
  58. this.int = new Uint256(values);
  59. break;
  60. default:
  61. throw TypeError(`invalid type: ${type}`);
  62. }
  63. this.type = type;
  64. }
  65. /**
  66. * @returns {number}
  67. * @throws {RangeError} if the value can't fit into a Number
  68. */
  69. toNumber() {
  70. const bi = this.int.toBigInt();
  71. if (bi > Number.MAX_SAFE_INTEGER || bi < Number.MIN_SAFE_INTEGER) {
  72. throw RangeError(
  73. `value ${bi} not in range for Number ` +
  74. `[${Number.MAX_SAFE_INTEGER}, ${Number.MIN_SAFE_INTEGER}]`
  75. );
  76. }
  77. return Number(bi);
  78. }
  79. /** @returns {bigint} */
  80. toBigInt() {
  81. return this.int.toBigInt();
  82. }
  83. /** @returns {xdr.ScVal} the integer encoded with `ScValType = I64` */
  84. toI64() {
  85. this._sizeCheck(64);
  86. const v = this.toBigInt();
  87. if (BigInt.asIntN(64, v) !== v) {
  88. throw RangeError(`value too large for i64: ${v}`);
  89. }
  90. return xdr.ScVal.scvI64(new xdr.Int64(v));
  91. }
  92. /** @returns {xdr.ScVal} the integer encoded with `ScValType = U64` */
  93. toU64() {
  94. this._sizeCheck(64);
  95. return xdr.ScVal.scvU64(
  96. new xdr.Uint64(BigInt.asUintN(64, this.toBigInt())) // reiterpret as unsigned
  97. );
  98. }
  99. /**
  100. * @returns {xdr.ScVal} the integer encoded with `ScValType = I128`
  101. * @throws {RangeError} if the value cannot fit in 128 bits
  102. */
  103. toI128() {
  104. this._sizeCheck(128);
  105. const v = this.int.toBigInt();
  106. const hi64 = BigInt.asIntN(64, v >> 64n); // encode top 64 w/ sign bit
  107. const lo64 = BigInt.asUintN(64, v); // grab btm 64, encode sign
  108. return xdr.ScVal.scvI128(
  109. new xdr.Int128Parts({
  110. hi: new xdr.Int64(hi64),
  111. lo: new xdr.Uint64(lo64)
  112. })
  113. );
  114. }
  115. /**
  116. * @returns {xdr.ScVal} the integer encoded with `ScValType = U128`
  117. * @throws {RangeError} if the value cannot fit in 128 bits
  118. */
  119. toU128() {
  120. this._sizeCheck(128);
  121. const v = this.int.toBigInt();
  122. return xdr.ScVal.scvU128(
  123. new xdr.UInt128Parts({
  124. hi: new xdr.Uint64(BigInt.asUintN(64, v >> 64n)),
  125. lo: new xdr.Uint64(BigInt.asUintN(64, v))
  126. })
  127. );
  128. }
  129. /** @returns {xdr.ScVal} the integer encoded with `ScValType = I256` */
  130. toI256() {
  131. const v = this.int.toBigInt();
  132. const hiHi64 = BigInt.asIntN(64, v >> 192n); // keep sign bit
  133. const hiLo64 = BigInt.asUintN(64, v >> 128n);
  134. const loHi64 = BigInt.asUintN(64, v >> 64n);
  135. const loLo64 = BigInt.asUintN(64, v);
  136. return xdr.ScVal.scvI256(
  137. new xdr.Int256Parts({
  138. hiHi: new xdr.Int64(hiHi64),
  139. hiLo: new xdr.Uint64(hiLo64),
  140. loHi: new xdr.Uint64(loHi64),
  141. loLo: new xdr.Uint64(loLo64)
  142. })
  143. );
  144. }
  145. /** @returns {xdr.ScVal} the integer encoded with `ScValType = U256` */
  146. toU256() {
  147. const v = this.int.toBigInt();
  148. const hiHi64 = BigInt.asUintN(64, v >> 192n); // encode sign bit
  149. const hiLo64 = BigInt.asUintN(64, v >> 128n);
  150. const loHi64 = BigInt.asUintN(64, v >> 64n);
  151. const loLo64 = BigInt.asUintN(64, v);
  152. return xdr.ScVal.scvU256(
  153. new xdr.UInt256Parts({
  154. hiHi: new xdr.Uint64(hiHi64),
  155. hiLo: new xdr.Uint64(hiLo64),
  156. loHi: new xdr.Uint64(loHi64),
  157. loLo: new xdr.Uint64(loLo64)
  158. })
  159. );
  160. }
  161. /** @returns {xdr.ScVal} the smallest interpretation of the stored value */
  162. toScVal() {
  163. switch (this.type) {
  164. case 'i64':
  165. return this.toI64();
  166. case 'i128':
  167. return this.toI128();
  168. case 'i256':
  169. return this.toI256();
  170. case 'u64':
  171. return this.toU64();
  172. case 'u128':
  173. return this.toU128();
  174. case 'u256':
  175. return this.toU256();
  176. default:
  177. throw TypeError(`invalid type: ${this.type}`);
  178. }
  179. }
  180. valueOf() {
  181. return this.int.valueOf();
  182. }
  183. toString() {
  184. return this.int.toString();
  185. }
  186. toJSON() {
  187. return {
  188. value: this.toBigInt().toString(),
  189. type: this.type
  190. };
  191. }
  192. _sizeCheck(bits) {
  193. if (this.int.size > bits) {
  194. throw RangeError(`value too large for ${bits} bits (${this.type})`);
  195. }
  196. }
  197. static isType(type) {
  198. switch (type) {
  199. case 'i64':
  200. case 'i128':
  201. case 'i256':
  202. case 'u64':
  203. case 'u128':
  204. case 'u256':
  205. return true;
  206. default:
  207. return false;
  208. }
  209. }
  210. /**
  211. * Convert the raw `ScValType` string (e.g. 'scvI128', generated by the XDR)
  212. * to a type description for {@link XdrLargeInt} construction (e.g. 'i128')
  213. *
  214. * @param {string} scvType the `xdr.ScValType` as a string
  215. * @returns {string} a suitable equivalent type to construct this object
  216. */
  217. static getType(scvType) {
  218. return scvType.slice(3).toLowerCase();
  219. }
  220. }