Home Reference Source

packages/oo7-substrate/src/codec.js

  1. const { TextDecoder } = require('text-encoding')
  2. const { ss58Decode } = require('./ss58')
  3. const { VecU8, AccountId, Hash, Signature, VoteThreshold, SlashPreference, Moment, Balance,
  4. BlockNumber, AccountIndex, Tuple, TransactionEra, Perbill, Permill } = require('./types')
  5. const { toLE, leToNumber, leToSigned, bytesToHex, hexToBytes } = require('./utils')
  6. const { metadata } = require('./metadata')
  7.  
  8. const transforms = {
  9. Legacy_RuntimeMetadata: { outerEvent: 'Legacy_OuterEventMetadata', modules: 'Vec<Legacy_RuntimeModuleMetadata>', outerDispatch: 'Legacy_OuterDispatchMetadata' },
  10. Legacy_OuterDispatchMetadata: { name: 'String', calls: 'Vec<Legacy_OuterDispatchCall>' },
  11. Legacy_OuterDispatchCall: { name: 'String', prefix: 'String', index: 'u16' },
  12. Legacy_RuntimeModuleMetadata: { prefix: 'String', module: 'Legacy_ModuleMetadata', storage: 'Option<Legacy_StorageMetadata>' },
  13. Legacy_StorageFunctionModifier: { _enum: [ 'Optional', 'Default' ] },
  14. Legacy_StorageFunctionTypeMap: { key: 'Type', value: 'Type' },
  15. Legacy_StorageFunctionType: { _enum: { Plain: 'Type', Map: 'Legacy_StorageFunctionTypeMap' } },
  16. Legacy_StorageFunctionMetadata: {
  17. name: 'String',
  18. modifier: 'Legacy_StorageFunctionModifier',
  19. type: 'Legacy_StorageFunctionType',
  20. default: 'Vec<u8>',
  21. documentation: 'Vec<String>',
  22. _post: x => {
  23. try {
  24. if (x.default) {
  25. x.default = decode(
  26. x.default,
  27. x.type.option === 'Plain' ? x.type.value : x.type.value.value
  28. )
  29. }
  30. }
  31. catch (e) {
  32. x.default = null
  33. }
  34. }
  35. },
  36. Legacy_StorageMetadata: { prefix: 'String', items: 'Vec<Legacy_StorageFunctionMetadata>' },
  37. Legacy_EventMetadata: { name: 'String', arguments: 'Vec<Type>', documentation: 'Vec<String>' },
  38. Legacy_OuterEventMetadata: { name: 'String', events: 'Vec<(String, Vec<Legacy_EventMetadata>)>' },
  39. Legacy_ModuleMetadata: { name: 'String', call: 'Legacy_CallMetadata' },
  40. Legacy_CallMetadata: { name: 'String', functions: 'Vec<Legacy_FunctionMetadata>' },
  41. Legacy_FunctionMetadata: { id: 'u16', name: 'String', arguments: 'Vec<Legacy_FunctionArgumentMetadata>', documentation: 'Vec<String>' },
  42. Legacy_FunctionArgumentMetadata: { name: 'String', type: 'Type' },
  43.  
  44. MetadataHead: { magic: 'u32', version: 'u8' },
  45. MetadataBody: { modules: 'Vec<MetadataModule>' },
  46. MetadataModule: {
  47. name: 'String',
  48. prefix: 'String',
  49. storage: 'Option<Vec<MetadataStorage>>',
  50. calls: 'Option<Vec<MetadataCall>>',
  51. events: 'Option<Vec<MetadataEvent>>',
  52. },
  53. MetadataStorage: {
  54. name: 'String',
  55. modifier: { _enum: [ 'Optional', 'Default' ] },
  56. type: { _enum: { Plain: 'Type', Map: { key: 'Type', value: 'Type' } } },
  57. default: 'Vec<u8>',
  58. documentation: 'Docs',
  59. _post: x => {
  60. try {
  61. if (x.default) {
  62. x.default = decode(
  63. x.default,
  64. x.type.option === 'Plain' ? x.type.value : x.type.value.value
  65. )
  66. }
  67. }
  68. catch (e) {
  69. x.default = null
  70. }
  71. }
  72. },
  73. MetadataCall: {
  74. name: 'String',
  75. arguments: 'Vec<MetadataCallArg>',
  76. documentation: 'Docs',
  77. },
  78. MetadataCallArg: { name: 'String', type: 'Type' },
  79. MetadataEvent: {
  80. name: 'String',
  81. arguments: 'Vec<Type>',
  82. documentation: 'Docs',
  83. },
  84. Docs: 'Vec<String>',
  85.  
  86. NewAccountOutcome: { _enum: [ 'NoHint', 'GoodHint', 'BadHint' ] },
  87. UpdateBalanceOutcome: { _enum: [ 'Updated', 'AccountKilled' ] },
  88.  
  89. Transaction: { version: 'u8', sender: 'Address', signature: 'Signature', index: 'Compact<Index>', era: 'TransactionEra', call: 'Call' },
  90. Phase: { _enum: { ApplyExtrinsic: 'u32', Finalization: undefined } },
  91. EventRecord: { phase: 'Phase', event: 'Event' },
  92.  
  93. "<LookupasStaticLookup>::Source": 'Address',
  94. "RawAddress<AccountId,AccountIndex>": 'Address',
  95. "Address<AccountId,AccountIndex>": 'Address',
  96. ParaId: 'u32',
  97. VoteIndex: 'u32',
  98. PropIndex: 'u32',
  99. ReferendumIndex: 'u32',
  100. Index: 'u64',
  101.  
  102. KeyValue: '(Vec<u8>, Vec<u8>)',
  103. ParaId: 'u32'
  104. };
  105.  
  106. function addCodecTransform(type, transform) {
  107. if (!transforms[type]) {
  108. transforms[type] = transform
  109. }
  110. }
  111.  
  112. var decodePrefix = '';
  113.  
  114. function decode(input, type) {
  115. if (typeof input.data === 'undefined') {
  116. input = { data: input };
  117. }
  118. if (typeof type === 'object') {
  119. let res = {};
  120. if (type instanceof Array) {
  121. // just a tuple
  122. res = new Tuple(type.map(t => decode(input, t)));
  123. } else if (!type._enum) {
  124. // a struct
  125. Object.keys(type).forEach(k => {
  126. if (k != '_post') {
  127. res[k] = decode(input, type[k])
  128. }
  129. });
  130. } else if (type._enum instanceof Array) {
  131. // simple enum
  132. let n = input.data[0];
  133. input.data = input.data.slice(1);
  134. res = { option: type._enum[n] };
  135. } else if (type._enum) {
  136. // enum
  137. let n = input.data[0];
  138. input.data = input.data.slice(1);
  139. let option = Object.keys(type._enum)[n];
  140. res = { option, value: typeof type._enum[option] === 'undefined' ? undefined : decode(input, type._enum[option]) };
  141. }
  142. if (type._post) {
  143. type._post(res)
  144. }
  145. return res;
  146. }
  147. type = type.replace(/ /g, '').replace(/^(T::)+/, '');
  148. if (type == 'EventRecord<Event>') {
  149. type = 'EventRecord'
  150. }
  151.  
  152. let reencodeCompact;
  153. let p1 = type.match(/^<([A-Z][A-Za-z0-9]*)asHasCompact>::Type$/);
  154. if (p1) {
  155. reencodeCompact = p1[1]
  156. }
  157. let p2 = type.match(/^Compact<([A-Za-z][A-Za-z0-9]*)>$/);
  158. if (p2) {
  159. reencodeCompact = p2[1]
  160. }
  161. if (reencodeCompact) {
  162. return decode(encode(decode(input, 'Compact'), reencodeCompact), reencodeCompact);
  163. }
  164.  
  165. let dataHex = bytesToHex(input.data.slice(0, 50));
  166. // console.log(decodePrefix + 'des >>>', type, dataHex);
  167. // decodePrefix += " ";
  168.  
  169. let res;
  170. let transform = transforms[type];
  171. if (transform) {
  172. res = decode(input, transform);
  173. res._type = type;
  174. } else {
  175. switch (type) {
  176. case 'Call':
  177. case 'Proposal': {
  178. throw "Cannot represent Call/Proposal"
  179. }
  180. case 'Event': {
  181. let events = metadata().outerEvent.events
  182. let moduleIndex = decode(input, 'u8')
  183. let module = events[moduleIndex][0]
  184. let eventIndex = decode(input, 'u8')
  185. let name = events[moduleIndex][1][eventIndex].name
  186. let args = decode(input, events[moduleIndex][1][eventIndex].arguments)
  187. res = { _type: 'Event', module, name, args }
  188. break
  189. }
  190. case 'AccountId': {
  191. res = new AccountId(input.data.slice(0, 32));
  192. input.data = input.data.slice(32);
  193. break;
  194. }
  195. case 'Hash': {
  196. res = new Hash(input.data.slice(0, 32));
  197. input.data = input.data.slice(32);
  198. break;
  199. }
  200. case 'Signature': {
  201. res = new Signature(input.data.slice(0,64));
  202. input.data = input.data.slice(64);
  203. break;
  204. }
  205. case 'Balance': {
  206. res = leToNumber(input.data.slice(0, 16));
  207. input.data = input.data.slice(16);
  208. res = new Balance(res);
  209. break;
  210. }
  211. case 'BlockNumber': {
  212. res = leToNumber(input.data.slice(0, 8));
  213. input.data = input.data.slice(8);
  214. res = new BlockNumber(res);
  215. break;
  216. }
  217. case 'AccountIndex': {
  218. res = leToNumber(input.data.slice(0, 4));
  219. input.data = input.data.slice(4);
  220. res = new AccountIndex(res);
  221. break;
  222. }
  223. case 'Moment': {
  224. let n = leToNumber(input.data.slice(0, 8));
  225. input.data = input.data.slice(8);
  226. res = new Moment(n);
  227. break;
  228. }
  229. case 'VoteThreshold': {
  230. const VOTE_THRESHOLD = ['SuperMajorityApprove', 'NotSuperMajorityAgainst', 'SimpleMajority'];
  231. res = new VoteThreshold(VOTE_THRESHOLD[input.data[0]]);
  232. input.data = input.data.slice(1);
  233. break;
  234. }
  235. case 'SlashPreference': {
  236. res = new SlashPreference(decode(input, 'u32'));
  237. break;
  238. }
  239. case 'Perbill': {
  240. res = new Perbill(decode(input, 'u32') / 1000000000.0);
  241. break;
  242. }
  243. case 'Permill': {
  244. res = new Permill(decode(input, 'u32') / 1000000.0);
  245. break;
  246. }
  247. case 'Compact': {
  248. let len;
  249. if (input.data[0] % 4 == 0) {
  250. // one byte
  251. res = input.data[0] >> 2;
  252. len = 1;
  253. } else if (input.data[0] % 4 == 1) {
  254. res = leToNumber(input.data.slice(0, 2)) >> 2;
  255. len = 2;
  256. } else if (input.data[0] % 4 == 2) {
  257. res = leToNumber(input.data.slice(0, 4)) >> 2;
  258. len = 4;
  259. } else {
  260. let n = (input.data[0] >> 2) + 4;
  261. res = leToNumber(input.data.slice(1, n + 1));
  262. len = 1 + n;
  263. }
  264. input.data = input.data.slice(len);
  265. break;
  266. }
  267. case 'u8':
  268. res = input.data.slice(0, 1);
  269. input.data = input.data.slice(1);
  270. break;
  271. case 'u16':
  272. res = leToNumber(input.data.slice(0, 2));
  273. input.data = input.data.slice(2);
  274. break;
  275. case 'u32': {
  276. res = leToNumber(input.data.slice(0, 4));
  277. input.data = input.data.slice(4);
  278. break;
  279. }
  280. case 'u64': {
  281. res = leToNumber(input.data.slice(0, 8));
  282. input.data = input.data.slice(8);
  283. break;
  284. }
  285. case 'u128': {
  286. res = leToNumber(input.data.slice(0, 16));
  287. input.data = input.data.slice(16);
  288. break;
  289. }
  290. case 'i8': {
  291. res = leToSigned(input.data.slice(0, 1));
  292. input.data = input.data.slice(1);
  293. break;
  294. }
  295. case 'i16':
  296. res = leToSigned(input.data.slice(0, 2));
  297. input.data = input.data.slice(2);
  298. break;
  299. case 'i32': {
  300. res = leToSigned(input.data.slice(0, 4));
  301. input.data = input.data.slice(4);
  302. break;
  303. }
  304. case 'i64': {
  305. res = leToSigned(input.data.slice(0, 8));
  306. input.data = input.data.slice(8);
  307. break;
  308. }
  309. case 'i128': {
  310. res = leToSigned(input.data.slice(0, 16));
  311. input.data = input.data.slice(16);
  312. break;
  313. }
  314. case 'bool': {
  315. res = !!input.data[0];
  316. input.data = input.data.slice(1);
  317. break;
  318. }
  319. case 'Vec<bool>': {
  320. let size = decode(input, 'Compact<u32>');
  321. res = [...input.data.slice(0, size)].map(a => !!a);
  322. input.data = input.data.slice(size);
  323. break;
  324. }
  325. case 'Vec<u8>': {
  326. let size = decode(input, 'Compact<u32>');
  327. res = input.data.slice(0, size);
  328. input.data = input.data.slice(size);
  329. break;
  330. }
  331. case 'String': {
  332. let size = decode(input, 'Compact<u32>');
  333. res = input.data.slice(0, size);
  334. input.data = input.data.slice(size);
  335. res = new TextDecoder("utf-8").decode(res);
  336. break;
  337. }
  338. case 'Type': {
  339. res = decode(input, 'String');
  340. while (res.indexOf('T::') != -1) {
  341. res = res.replace('T::', '');
  342. }
  343. res = res.match(/^Box<.*>$/) ? res.slice(4, -1) : res;
  344. break;
  345. }
  346. default: {
  347. let v = type.match(/^Vec<(.*)>$/);
  348. if (v) {
  349. let size = decode(input, 'Compact<u32>');
  350. res = [...new Array(size)].map(() => decode(input, v[1]));
  351. break;
  352. }
  353. let o = type.match(/^Option<(.*)>$/);
  354. if (o) {
  355. let some = decode(input, 'bool');
  356. if (some) {
  357. res = decode(input, o[1]);
  358. } else {
  359. res = null;
  360. }
  361. break;
  362. }
  363. let t = type.match(/^\((.*)\)$/);
  364. if (t) {
  365. res = new Tuple(...decode(input, t[1].split(',')));
  366. break;
  367. }
  368. throw 'Unknown type to decode: ' + type;
  369. }
  370. }
  371. }
  372. // decodePrefix = decodePrefix.substr(3);
  373. // console.log(decodePrefix + 'des <<<', type, res);
  374. return res;
  375. }
  376.  
  377. function encode(value, type = null) {
  378. // if an array then just concat
  379. if (type instanceof Array) {
  380. if (value instanceof Array) {
  381. let x = value.map((i, index) => encode(i, type[index]));
  382. let res = new Uint8Array();
  383. x.forEach(x => {
  384. r = new Uint8Array(res.length + x.length);
  385. r.set(res)
  386. r.set(x, res.length)
  387. res = r
  388. })
  389. return res
  390. } else {
  391. throw 'If type is array, value must be too'
  392. }
  393. }
  394. if (typeof value == 'object' && !type && value._type) {
  395. type = value._type
  396. }
  397. if (typeof type != 'string') {
  398. throw 'type must be either an array or a string'
  399. }
  400. type = type.replace(/ /g, '').replace(/^(T::)+/, '');
  401.  
  402. if (typeof value == 'string' && value.startsWith('0x')) {
  403. value = hexToBytes(value)
  404. }
  405.  
  406. if (transforms[type]) {
  407. let transform = transforms[type]
  408. if (transform instanceof Array || typeof transform == 'string') {
  409. // just a tuple or string
  410. return encode(value, transform)
  411. } else if (!transform._enum) {
  412. // a struct
  413. let keys = []
  414. let types = []
  415. Object.keys(transform).forEach(k => {
  416. keys.push(value[k])
  417. types.push(transform[k])
  418. })
  419. return encode(keys, types)
  420. } else if (transform._enum instanceof Array) {
  421. // simple enum
  422. return new Uint8Array([transform._enum.indexOf(value.option)])
  423. } else if (transform._enum) {
  424. // enum
  425. let index = Object.keys(transform._enum).indexOf(value.option)
  426. let value = encode(value.value, transform._enum[value.option])
  427. return new Uint8Array([index, ...value])
  428. }
  429. }
  430.  
  431. // other type-specific transforms
  432. if (type == 'Vec<u8>') {
  433. if (typeof value == 'object' && value instanceof Uint8Array) {
  434. return new Uint8Array([...encode(value.length, 'Compact<u32>'), ...value])
  435. }
  436. }
  437.  
  438. let match_vec = type.match(/^Vec<(.*)>$/);
  439. if (match_vec) {
  440. if (value instanceof Array) {
  441. let res = new Uint8Array([...encode(value.length, 'Compact<u32>')])
  442. value.forEach(v => {
  443. let x = encode(v, match_vec[1])
  444. r = new Uint8Array(res.length + x.length)
  445. r.set(res)
  446. r.set(x, res.length)
  447. res = r
  448. })
  449. return res
  450. }
  451. }
  452.  
  453. let t = type.match(/^\((.*)\)$/)
  454. if (t) {
  455. return encode(value, t[1].split(','))
  456. }
  457.  
  458. if (type == 'Address') {
  459. if (typeof value == 'string') {
  460. value = ss58Decode(value)
  461. }
  462. if (typeof value == 'object' && value instanceof Uint8Array && value.length == 32) {
  463. return new Uint8Array([0xff, ...value])
  464. }
  465. if (typeof value == 'number' || value instanceof AccountIndex) {
  466. if (value < 0xf0) {
  467. return new Uint8Array([value])
  468. } else if (value < 1 << 16) {
  469. return new Uint8Array([0xfc, ...toLE(value, 2)])
  470. } else if (value < 1 << 32) {
  471. return new Uint8Array([0xfd, ...toLE(value, 4)])
  472. } else if (value < 1 << 64) {
  473. return new Uint8Array([0xfe, ...toLE(value, 8)])
  474. }
  475. }
  476. }
  477.  
  478. if (type == 'AccountId') {
  479. if (typeof value == 'string') {
  480. return ss58Decode(value);
  481. }
  482. if (value instanceof Uint8Array && value.length == 32) {
  483. return value
  484. }
  485. }
  486.  
  487. if (typeof value == 'number' || (typeof value == 'string' && +value + '' == value)) {
  488. value = +value
  489. switch (type) {
  490. case 'Balance':
  491. case 'u128':
  492. case 'i128':
  493. return toLE(value, 16)
  494. case 'u64':
  495. case 'i64':
  496. return toLE(value, 8)
  497. case 'AccountIndex':
  498. case 'u32':
  499. case 'i32':
  500. return toLE(value, 4)
  501. case 'u16':
  502. case 'i16':
  503. return toLE(value, 2)
  504. case 'u8':
  505. case 'i8':
  506. return toLE(value, 1)
  507. default:
  508. break
  509. }
  510. }
  511.  
  512. if (value instanceof AccountIndex && type == 'AccountIndex') {
  513. return toLE(value, 4)
  514. }
  515.  
  516. if ((value instanceof Perbill || typeof value === 'number') && type == 'Perbill') {
  517. return toLE(value * 1000000000, 4)
  518. }
  519.  
  520. if ((value instanceof Permill || typeof value === 'number') && type == 'Permill') {
  521. return toLE(value * 1000000, 4)
  522. }
  523.  
  524. if (value instanceof Uint8Array) {
  525. if (type == 'Signature' && value.length == 64) {
  526. return value
  527. }
  528. if (type == 'Hash' && value.length == 32) {
  529. return value
  530. }
  531. }
  532.  
  533. if (type == 'TransactionEra' && value instanceof TransactionEra) {
  534. return value.encode()
  535. } else if (type == 'TransactionEra') {
  536. console.error("TxEra::encode bad", type, value)
  537. }
  538. if (type.match(/^<[A-Z][A-Za-z0-9]*asHasCompact>::Type$/) || type.match(/^Compact<[A-Za-z][A-Za-z0-9]*>$/) || type === 'Compact') {
  539. if (value < 1 << 6) {
  540. return new Uint8Array([value << 2])
  541. } else if (value < 1 << 14) {
  542. return toLE((value << 2) + 1, 2)
  543. } else if (value < 1 << 30) {
  544. return toLE((value << 2) + 2, 4)
  545. } else {
  546. let bytes = 0;
  547. for (let v = value; v > 0; v = Math.floor(v / 256)) { ++bytes }
  548. return new Uint8Array([3 + ((bytes - 4) << 2), ...toLE(value, bytes)])
  549. }
  550. }
  551.  
  552. if (type == 'bool') {
  553. return new Uint8Array([value ? 1 : 0])
  554. }
  555.  
  556. if (typeof type == 'string' && type.match(/\(.*\)/)) {
  557. return encode(value, type.substr(1, type.length - 2).split(','))
  558. }
  559.  
  560. // Maybe it's pre-encoded?
  561. if (typeof value == 'object' && value instanceof Uint8Array) {
  562. switch (type) {
  563. case 'Call':
  564. case 'Proposal':
  565. break
  566. default:
  567. console.warn(`Value passed apparently pre-encoded without whitelisting ${type}`)
  568. }
  569. return value
  570. }
  571.  
  572. throw `Value cannot be encoded as type: ${value}, ${type}`
  573. }
  574.  
  575. module.exports = { decode, encode, addCodecTransform }