index.ts 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. import {
  2. blob,
  3. Layout as LayoutCls,
  4. offset,
  5. seq,
  6. struct,
  7. u32,
  8. u8,
  9. union,
  10. } from "buffer-layout";
  11. import { PublicKey } from "@solana/web3.js";
  12. import BN from "bn.js";
  13. export {
  14. u8,
  15. s8 as i8,
  16. u16,
  17. s16 as i16,
  18. u32,
  19. s32 as i32,
  20. f32,
  21. f64,
  22. struct,
  23. } from "buffer-layout";
  24. export interface Layout<T> {
  25. span: number;
  26. property?: string;
  27. decode(b: Buffer, offset?: number): T;
  28. encode(src: T, b: Buffer, offset?: number): number;
  29. getSpan(b: Buffer, offset?: number): number;
  30. replicate(name: string): this;
  31. }
  32. class BNLayout extends LayoutCls<BN> {
  33. blob: Layout<Buffer>;
  34. signed: boolean;
  35. constructor(span: number, signed: boolean, property?: string) {
  36. super(span, property);
  37. this.blob = blob(span);
  38. this.signed = signed;
  39. }
  40. decode(b: Buffer, offset = 0) {
  41. const num = new BN(this.blob.decode(b, offset), 10, "le");
  42. if (this.signed) {
  43. return num.fromTwos(this.span * 8).clone();
  44. }
  45. return num;
  46. }
  47. encode(src: BN, b: Buffer, offset = 0) {
  48. if (this.signed) {
  49. src = src.toTwos(this.span * 8);
  50. }
  51. return this.blob.encode(
  52. src.toArrayLike(Buffer, "le", this.span),
  53. b,
  54. offset
  55. );
  56. }
  57. }
  58. export function u64(property?: string): Layout<BN> {
  59. return new BNLayout(8, false, property);
  60. }
  61. export function i64(property?: string): Layout<BN> {
  62. return new BNLayout(8, true, property);
  63. }
  64. export function u128(property?: string): Layout<BN> {
  65. return new BNLayout(16, false, property);
  66. }
  67. export function i128(property?: string): Layout<BN> {
  68. return new BNLayout(16, true, property);
  69. }
  70. export function u256(property?: string): Layout<BN> {
  71. return new BNLayout(32, false, property);
  72. }
  73. export function i256(property?: string): Layout<BN> {
  74. return new BNLayout(32, true, property);
  75. }
  76. class WrappedLayout<T, U> extends LayoutCls<U> {
  77. layout: Layout<T>;
  78. decoder: (data: T) => U;
  79. encoder: (src: U) => T;
  80. constructor(
  81. layout: Layout<T>,
  82. decoder: (data: T) => U,
  83. encoder: (src: U) => T,
  84. property?: string
  85. ) {
  86. super(layout.span, property);
  87. this.layout = layout;
  88. this.decoder = decoder;
  89. this.encoder = encoder;
  90. }
  91. decode(b: Buffer, offset?: number): U {
  92. return this.decoder(this.layout.decode(b, offset));
  93. }
  94. encode(src: U, b: Buffer, offset?: number): number {
  95. return this.layout.encode(this.encoder(src), b, offset);
  96. }
  97. getSpan(b: Buffer, offset?: number): number {
  98. return this.layout.getSpan(b, offset);
  99. }
  100. }
  101. export function publicKey(property?: string): Layout<PublicKey> {
  102. return new WrappedLayout(
  103. blob(32),
  104. (b: Buffer) => new PublicKey(b),
  105. (key: PublicKey) => key.toBuffer(),
  106. property
  107. );
  108. }
  109. class OptionLayout<T> extends LayoutCls<T | null> {
  110. layout: Layout<T>;
  111. discriminator: Layout<number>;
  112. constructor(layout: Layout<T>, property?: string) {
  113. super(-1, property);
  114. this.layout = layout;
  115. this.discriminator = u8();
  116. }
  117. encode(src: T | null, b: Buffer, offset = 0): number {
  118. if (src === null || src === undefined) {
  119. return this.discriminator.encode(0, b, offset);
  120. }
  121. this.discriminator.encode(1, b, offset);
  122. return this.layout.encode(src, b, offset + 1) + 1;
  123. }
  124. decode(b: Buffer, offset = 0): T | null {
  125. const discriminator = this.discriminator.decode(b, offset);
  126. if (discriminator === 0) {
  127. return null;
  128. } else if (discriminator === 1) {
  129. return this.layout.decode(b, offset + 1);
  130. }
  131. throw new Error("Invalid option " + this.property);
  132. }
  133. getSpan(b: Buffer, offset = 0): number {
  134. const discriminator = this.discriminator.decode(b, offset);
  135. if (discriminator === 0) {
  136. return 1;
  137. } else if (discriminator === 1) {
  138. return this.layout.getSpan(b, offset + 1) + 1;
  139. }
  140. throw new Error("Invalid option " + this.property);
  141. }
  142. }
  143. export function option<T>(
  144. layout: Layout<T>,
  145. property?: string
  146. ): Layout<T | null> {
  147. return new OptionLayout<T>(layout, property);
  148. }
  149. export function bool(property?: string): Layout<boolean> {
  150. return new WrappedLayout(u8(), decodeBool, encodeBool, property);
  151. }
  152. function decodeBool(value: number): boolean {
  153. if (value === 0) {
  154. return false;
  155. } else if (value === 1) {
  156. return true;
  157. }
  158. throw new Error("Invalid bool: " + value);
  159. }
  160. function encodeBool(value: boolean): number {
  161. return value ? 1 : 0;
  162. }
  163. export function vec<T>(
  164. elementLayout: Layout<T>,
  165. property?: string
  166. ): Layout<T[]> {
  167. const length = u32("length");
  168. const layout: Layout<{ values: T[] }> = struct([
  169. length,
  170. seq(elementLayout, offset(length, -length.span), "values"),
  171. ]);
  172. return new WrappedLayout(
  173. layout,
  174. ({ values }) => values,
  175. (values) => ({ values }),
  176. property
  177. );
  178. }
  179. export function tagged<T>(
  180. tag: BN,
  181. layout: Layout<T>,
  182. property?: string
  183. ): Layout<T> {
  184. const wrappedLayout: Layout<{ tag: BN; data: T }> = struct([
  185. u64("tag"),
  186. layout.replicate("data"),
  187. ]);
  188. function decodeTag({ tag: receivedTag, data }: { tag: BN; data: T }) {
  189. if (!receivedTag.eq(tag)) {
  190. throw new Error(
  191. "Invalid tag, expected: " +
  192. tag.toString("hex") +
  193. ", got: " +
  194. receivedTag.toString("hex")
  195. );
  196. }
  197. return data;
  198. }
  199. return new WrappedLayout(
  200. wrappedLayout,
  201. decodeTag,
  202. (data) => ({ tag, data }),
  203. property
  204. );
  205. }
  206. export function vecU8(property?: string): Layout<Buffer> {
  207. const length = u32("length");
  208. const layout: Layout<{ data: Buffer }> = struct([
  209. length,
  210. blob(offset(length, -length.span), "data"),
  211. ]);
  212. return new WrappedLayout(
  213. layout,
  214. ({ data }) => data,
  215. (data) => ({ data }),
  216. property
  217. );
  218. }
  219. export function str(property?: string): Layout<string> {
  220. return new WrappedLayout(
  221. vecU8(),
  222. (data) => data.toString("utf-8"),
  223. (s) => Buffer.from(s, "utf-8"),
  224. property
  225. );
  226. }
  227. export interface EnumLayout<T> extends Layout<T> {
  228. registry: Record<string, Layout<any>>;
  229. }
  230. export function rustEnum<T>(
  231. variants: Layout<any>[],
  232. property?: string,
  233. discriminant?: Layout<any>
  234. ): EnumLayout<T> {
  235. const unionLayout = union(discriminant ?? u8(), property);
  236. variants.forEach((variant, index) =>
  237. unionLayout.addVariant(index, variant, variant.property)
  238. );
  239. return unionLayout;
  240. }
  241. export function array<T>(
  242. elementLayout: Layout<T>,
  243. length: number,
  244. property?: string
  245. ): Layout<T[]> {
  246. const layout: Layout<{ values: T[] }> = struct([
  247. seq(elementLayout, length, "values"),
  248. ]);
  249. return new WrappedLayout(
  250. layout,
  251. ({ values }) => values,
  252. (values) => ({ values }),
  253. property
  254. );
  255. }
  256. class MapEntryLayout<K, V> extends LayoutCls<[K, V]> {
  257. keyLayout: Layout<K>;
  258. valueLayout: Layout<V>;
  259. constructor(keyLayout: Layout<K>, valueLayout: Layout<V>, property?: string) {
  260. super(keyLayout.span + valueLayout.span, property);
  261. this.keyLayout = keyLayout;
  262. this.valueLayout = valueLayout;
  263. }
  264. decode(b: Buffer, offset?: number): [K, V] {
  265. offset = offset || 0;
  266. const key = this.keyLayout.decode(b, offset);
  267. const value = this.valueLayout.decode(
  268. b,
  269. offset + this.keyLayout.getSpan(b, offset)
  270. );
  271. return [key, value];
  272. }
  273. encode(src: [K, V], b: Buffer, offset?: number): number {
  274. offset = offset || 0;
  275. const keyBytes = this.keyLayout.encode(src[0], b, offset);
  276. const valueBytes = this.valueLayout.encode(src[1], b, offset + keyBytes);
  277. return keyBytes + valueBytes;
  278. }
  279. getSpan(b: Buffer, offset?: number): number {
  280. return (
  281. this.keyLayout.getSpan(b, offset) + this.valueLayout.getSpan(b, offset)
  282. );
  283. }
  284. }
  285. export function map<K, V>(
  286. keyLayout: Layout<K>,
  287. valueLayout: Layout<V>,
  288. property?: string
  289. ): Layout<Map<K, V>> {
  290. const length = u32("length");
  291. const layout: Layout<{ values: [K, V][] }> = struct([
  292. length,
  293. seq(
  294. new MapEntryLayout(keyLayout, valueLayout),
  295. offset(length, -length.span),
  296. "values"
  297. ),
  298. ]);
  299. return new WrappedLayout(
  300. layout,
  301. ({ values }) => new Map(values),
  302. (values) => ({ values: Array.from(values.entries()) }),
  303. property
  304. );
  305. }