common.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. import { DatabaseManager, SubstrateEvent, SubstrateExtrinsic, ExtrinsicArg } from '@joystream/hydra-common'
  2. import { Bytes } from '@polkadot/types'
  3. import { Network } from 'query-node/dist/model'
  4. import { BaseModel } from '@joystream/warthog'
  5. import { metaToObject } from '@joystream/metadata-protobuf/utils'
  6. import { AnyMetadataClass, DecodedMetadataObject } from '@joystream/metadata-protobuf/types'
  7. export const CURRENT_NETWORK = Network.GIZA
  8. /*
  9. Simple logger enabling error and informational reporting.
  10. FIXME: `Logger` class will not be needed in the future when Hydra v3 will be released.
  11. Hydra will provide logger instance and relevant code using `Logger` should be refactored.
  12. */
  13. class Logger {
  14. /*
  15. Log significant event.
  16. */
  17. info(message: string, data?: unknown) {
  18. console.log(message, data)
  19. }
  20. /*
  21. Log significant error.
  22. */
  23. error(message: string, data?: unknown) {
  24. console.error(message, data)
  25. }
  26. }
  27. export const logger = new Logger()
  28. /*
  29. Reports that insurmountable inconsistent state has been encountered and throws an exception.
  30. */
  31. export function inconsistentState(extraInfo: string, data?: unknown): never {
  32. const errorMessage = 'Inconsistent state: ' + extraInfo
  33. // log error
  34. logger.error(errorMessage, data)
  35. throw errorMessage
  36. }
  37. /*
  38. Reports that insurmountable unexpected data has been encountered and throws an exception.
  39. */
  40. export function unexpectedData(extraInfo: string, data?: unknown): never {
  41. const errorMessage = 'Unexpected data: ' + extraInfo
  42. // log error
  43. logger.error(errorMessage, data)
  44. throw errorMessage
  45. }
  46. /*
  47. Reports that metadata inserted by the user are not entirely valid, but the problem can be overcome.
  48. */
  49. export function invalidMetadata(extraInfo: string, data?: unknown): void {
  50. const errorMessage = 'Invalid metadata: ' + extraInfo
  51. // log error
  52. logger.info(errorMessage, data)
  53. }
  54. /// //////////////// Sudo extrinsic calls ///////////////////////////////////////
  55. // soft-peg interface for typegen-generated `*Call` types
  56. export interface IGenericExtrinsicObject<T> {
  57. readonly extrinsic: SubstrateExtrinsic
  58. readonly expectedArgTypes: string[]
  59. args: T
  60. }
  61. // arguments for calling extrinsic as sudo
  62. export interface ISudoCallArgs<T> extends ExtrinsicArg {
  63. args: T
  64. callIndex: string
  65. }
  66. /*
  67. Extracts extrinsic arguments from the Substrate event. Supports both direct extrinsic calls and sudo calls.
  68. */
  69. export function extractExtrinsicArgs<DataParams, EventObject extends IGenericExtrinsicObject<DataParams>>(
  70. rawEvent: SubstrateEvent,
  71. callFactoryConstructor: new (event: SubstrateEvent) => EventObject,
  72. // in ideal world this parameter would not be needed, but there is no way to associate parameters
  73. // used in sudo to extrinsic parameters without it
  74. argsIndeces: Record<keyof DataParams, number>
  75. ): EventObject['args'] {
  76. const CallFactory = callFactoryConstructor
  77. // this is equal to DataParams but only this notation works properly
  78. // escape when extrinsic info is not available
  79. if (!rawEvent.extrinsic) {
  80. throw new Error('Invalid event - no extrinsic set') // this should never happen
  81. }
  82. // regural extrinsic call?
  83. if (rawEvent.extrinsic.section !== 'sudo') {
  84. return new CallFactory(rawEvent).args
  85. }
  86. // sudo extrinsic call
  87. const callArgs = extractSudoCallParameters<DataParams>(rawEvent)
  88. // convert naming convention (underscore_names to camelCase)
  89. const clearArgs = Object.keys(callArgs.args).reduce((acc, key) => {
  90. const formattedName = key.replace(/_([a-z])/g, (tmp) => tmp[1].toUpperCase())
  91. acc[formattedName] = callArgs.args[key]
  92. return acc
  93. }, {} as DataParams)
  94. // prepare partial event object
  95. const partialEvent = {
  96. extrinsic: ({
  97. args: Object.keys(argsIndeces).reduce((acc, key) => {
  98. acc[argsIndeces[key]] = {
  99. value: clearArgs[key],
  100. }
  101. return acc
  102. }, [] as unknown[]),
  103. } as unknown) as SubstrateExtrinsic,
  104. } as SubstrateEvent
  105. // create event object and extract processed args
  106. const finalArgs = new CallFactory(partialEvent).args
  107. return finalArgs
  108. }
  109. /*
  110. Extracts extrinsic call parameters used inside of sudo call.
  111. */
  112. export function extractSudoCallParameters<DataParams>(rawEvent: SubstrateEvent): ISudoCallArgs<DataParams> {
  113. if (!rawEvent.extrinsic) {
  114. throw new Error('Invalid event - no extrinsic set') // this should never happen
  115. }
  116. // see Substrate's sudo frame for more info about sudo extrinsics and `call` argument index
  117. const argIndex =
  118. false ||
  119. (rawEvent.extrinsic.method === 'sudoAs' && 1) || // who, *call*
  120. (rawEvent.extrinsic.method === 'sudo' && 0) || // *call*
  121. (rawEvent.extrinsic.method === 'sudoUncheckedWeight' && 0) // *call*, _weight
  122. // ensure `call` argument was found
  123. if (argIndex === false) {
  124. // this could possibly happen in sometime in future if new sudo options are introduced in Substrate
  125. throw new Error('Not implemented situation with sudo')
  126. }
  127. // typecast call arguments
  128. const callArgs = (rawEvent.extrinsic.args[argIndex].value as unknown) as ISudoCallArgs<DataParams>
  129. return callArgs
  130. }
  131. export function deserializeMetadata<T>(
  132. metadataType: AnyMetadataClass<T>,
  133. metadataBytes: Bytes
  134. ): DecodedMetadataObject<T> | null {
  135. try {
  136. return metaToObject(metadataType, metadataType.decode(metadataBytes.toU8a(true)))
  137. } catch (e) {
  138. invalidMetadata(`Cannot deserialize ${metadataType.name}! Provided bytes: (${metadataBytes.toHex()})`)
  139. return null
  140. }
  141. }
  142. export function bytesToString(b: Bytes): string {
  143. return (
  144. Buffer.from(b.toU8a(true))
  145. .toString()
  146. // eslint-disable-next-line no-control-regex
  147. .replace(/\u0000/g, '')
  148. )
  149. }
  150. export function perpareString(s: string): string {
  151. // eslint-disable-next-line no-control-regex
  152. return s.replace(/\u0000/g, '')
  153. }
  154. export function hasValuesForProperties<
  155. T extends Record<string, unknown>,
  156. P extends keyof T & string,
  157. PA extends readonly P[]
  158. >(obj: T, props: PA): obj is T & { [K in PA[number]]: NonNullable<K> } {
  159. props.forEach((p) => {
  160. if (obj[p] === null || obj[p] === undefined) {
  161. return false
  162. }
  163. })
  164. return true
  165. }
  166. type EntityClass<T extends BaseModel> = {
  167. new (): T
  168. name: string
  169. }
  170. export type RelationsArr<T extends BaseModel> = Exclude<
  171. keyof T & string,
  172. { [K in keyof T]: T[K] extends BaseModel | undefined ? '' : T[K] extends BaseModel[] | undefined ? '' : K }[keyof T]
  173. >[]
  174. export async function getById<T extends BaseModel>(
  175. store: DatabaseManager,
  176. entityClass: EntityClass<T>,
  177. id: string,
  178. relations?: RelationsArr<T>
  179. ): Promise<T> {
  180. const result = await store.get(entityClass, { where: { id }, relations })
  181. if (!result) {
  182. throw new Error(`Expected ${entityClass.name} not found by ID: ${id}`)
  183. }
  184. return result
  185. }
  186. export function deterministicEntityId(createdInEvent: SubstrateEvent, additionalIdentifier?: string | number): string {
  187. return (
  188. `${createdInEvent.blockNumber}-${createdInEvent.indexInBlock}` +
  189. (additionalIdentifier ? `-${additionalIdentifier}` : '')
  190. )
  191. }