event.ts 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import { Buffer } from "buffer";
  2. import * as base64 from "base64-js";
  3. import { Layout } from "buffer-layout";
  4. import { sha256 } from "js-sha256";
  5. import { Idl, IdlEvent, IdlTypeDef } from "../../idl.js";
  6. import { Event, EventData } from "../../program/event.js";
  7. import { IdlCoder } from "./idl.js";
  8. import { EventCoder } from "../index.js";
  9. import { FeatureSet } from "../../utils/features";
  10. export class BorshEventCoder implements EventCoder {
  11. /**
  12. * Maps account type identifier to a layout.
  13. */
  14. private layouts: Map<string, Layout>;
  15. /**
  16. * Maps base64 encoded event discriminator to event name.
  17. */
  18. private discriminators: Map<string, string>;
  19. /**
  20. * Header configuration.
  21. */
  22. private header: EventHeader;
  23. public constructor(idl: Idl) {
  24. if (idl.events === undefined) {
  25. this.layouts = new Map();
  26. return;
  27. }
  28. this.header = new EventHeader(features);
  29. const layouts: [string, Layout<any>][] = idl.events.map((event) => {
  30. let eventTypeDef: IdlTypeDef = {
  31. name: event.name,
  32. type: {
  33. kind: "struct",
  34. fields: event.fields.map((f) => {
  35. return { name: f.name, type: f.type };
  36. }),
  37. },
  38. };
  39. return [event.name, IdlCoder.typeDefLayout(eventTypeDef, idl.types)];
  40. });
  41. this.layouts = new Map(layouts);
  42. this.discriminators = new Map<string, string>(
  43. idl.events === undefined
  44. ? []
  45. : idl.events.map((e) => [
  46. base64.fromByteArray(this.header.discriminator(e.name)),
  47. e.name,
  48. ])
  49. );
  50. }
  51. public decode<E extends IdlEvent = IdlEvent, T = Record<string, never>>(
  52. log: string
  53. ): Event<E, T> | null {
  54. let logArr: Buffer;
  55. // This will throw if log length is not a multiple of 4.
  56. try {
  57. logArr = Buffer.from(base64.toByteArray(log));
  58. } catch (e) {
  59. return null;
  60. }
  61. const disc = base64.fromByteArray(this.header.parseDiscriminator(logArr));
  62. // Only deserialize if the discriminator implies a proper event.
  63. const eventName = this.discriminators.get(disc);
  64. if (eventName === undefined) {
  65. return null;
  66. }
  67. const layout = this.layouts.get(eventName);
  68. if (!layout) {
  69. throw new Error(`Unknown event: ${eventName}`);
  70. }
  71. const data = layout.decode(logArr.slice(this.header.size())) as EventData<
  72. E["fields"][number],
  73. T
  74. >;
  75. return { data, name: eventName };
  76. }
  77. }
  78. export function eventDiscriminator(name: string): Buffer {
  79. return this.header.discriminator(name);
  80. }
  81. class EventHeader {
  82. constructor(private _features: FeatureSet) {}
  83. public parseDiscriminator(data: Buffer): Buffer {
  84. if (this._features.deprecatedLayout) {
  85. return data.slice(0, 8);
  86. } else {
  87. return data.slice(0, 4);
  88. }
  89. }
  90. public size(): number {
  91. if (this._features.deprecatedLayout) {
  92. return 8;
  93. } else {
  94. return 4;
  95. }
  96. }
  97. public discriminator(name: string): Buffer {
  98. if (this._features.deprecatedLayout) {
  99. return Buffer.from(sha256.digest(`event:${name}`)).slice(0, 8);
  100. } else {
  101. return Buffer.from(sha256.digest(`event:${name}`)).slice(0, 4);
  102. }
  103. }
  104. }