UNPKG

7.81 kBJavaScriptView Raw
1import { Enum, Raw, Tuple, U64 } from '@polkadot/types-codec';
2import { bnToBn, formatNumber, hexToU8a, isHex, isObject, isU8a, u8aToBn, u8aToU8a } from '@polkadot/util';
3import { IMMORTAL_ERA } from './constants.js';
4function getTrailingZeros(period) {
5 const binary = period.toString(2);
6 let index = 0;
7 while (binary[binary.length - 1 - index] === '0') {
8 index++;
9 }
10 return index;
11}
12/** @internal */
13function decodeMortalEra(registry, value) {
14 if (isU8a(value) || isHex(value) || Array.isArray(value)) {
15 return decodeMortalU8a(registry, u8aToU8a(value));
16 }
17 else if (!value) {
18 return [new U64(registry), new U64(registry)];
19 }
20 else if (isObject(value)) {
21 return decodeMortalObject(registry, value);
22 }
23 throw new Error('Invalid data passed to Mortal era');
24}
25/** @internal */
26function decodeMortalObject(registry, value) {
27 const { current, period } = value;
28 let calPeriod = Math.pow(2, Math.ceil(Math.log2(period)));
29 calPeriod = Math.min(Math.max(calPeriod, 4), 1 << 16);
30 const phase = current % calPeriod;
31 const quantizeFactor = Math.max(calPeriod >> 12, 1);
32 const quantizedPhase = phase / quantizeFactor * quantizeFactor;
33 return [new U64(registry, calPeriod), new U64(registry, quantizedPhase)];
34}
35/** @internal */
36function decodeMortalU8a(registry, value) {
37 if (value.length === 0) {
38 return [new U64(registry), new U64(registry)];
39 }
40 const first = u8aToBn(value.subarray(0, 1)).toNumber();
41 const second = u8aToBn(value.subarray(1, 2)).toNumber();
42 const encoded = first + (second << 8);
43 const period = 2 << (encoded % (1 << 4));
44 const quantizeFactor = Math.max(period >> 12, 1);
45 const phase = (encoded >> 4) * quantizeFactor;
46 if (period < 4 || phase >= period) {
47 throw new Error('Invalid data passed to Mortal era');
48 }
49 return [new U64(registry, period), new U64(registry, phase)];
50}
51/** @internal */
52function decodeExtrinsicEra(value = new Uint8Array()) {
53 if (isU8a(value)) {
54 return (!value.length || value[0] === 0)
55 ? new Uint8Array([0])
56 : new Uint8Array([1, value[0], value[1]]);
57 }
58 else if (!value) {
59 return new Uint8Array([0]);
60 }
61 else if (value instanceof GenericExtrinsicEra) {
62 return decodeExtrinsicEra(value.toU8a());
63 }
64 else if (isHex(value)) {
65 return decodeExtrinsicEra(hexToU8a(value));
66 }
67 else if (isObject(value)) {
68 const entries = Object.entries(value).map(([k, v]) => [k.toLowerCase(), v]);
69 const mortal = entries.find(([k]) => k.toLowerCase() === 'mortalera');
70 const immortal = entries.find(([k]) => k.toLowerCase() === 'immortalera');
71 // this is to de-serialize from JSON
72 return mortal
73 ? { MortalEra: mortal[1] }
74 : immortal
75 ? { ImmortalEra: immortal[1] }
76 : { MortalEra: value };
77 }
78 throw new Error('Invalid data passed to Era');
79}
80/**
81 * @name ImmortalEra
82 * @description
83 * The ImmortalEra for an extrinsic
84 */
85export class ImmortalEra extends Raw {
86 constructor(registry, _value) {
87 // For immortals, we always provide the known value (i.e. treated as a
88 // constant no matter how it is constructed - it is a fixed structure)
89 super(registry, IMMORTAL_ERA);
90 }
91}
92/**
93 * @name MortalEra
94 * @description
95 * The MortalEra for an extrinsic, indicating period and phase
96 */
97export class MortalEra extends Tuple {
98 constructor(registry, value) {
99 super(registry, {
100 period: U64,
101 phase: U64
102 }, decodeMortalEra(registry, value));
103 }
104 /**
105 * @description Encoded length for mortals occupy 2 bytes, different from the actual Tuple since it is encoded. This is a shortcut fro `toU8a().length`
106 */
107 get encodedLength() {
108 return 2;
109 }
110 /**
111 * @description The period of this Mortal wraps as a [[U64]]
112 */
113 get period() {
114 return this[0];
115 }
116 /**
117 * @description The phase of this Mortal wraps as a [[U64]]
118 */
119 get phase() {
120 return this[1];
121 }
122 /**
123 * @description Converts the Object to to a human-friendly JSON, with additional fields, expansion and formatting of information
124 */
125 toHuman() {
126 return {
127 period: formatNumber(this.period),
128 phase: formatNumber(this.phase)
129 };
130 }
131 /**
132 * @description Returns a JSON representation of the actual value
133 */
134 toJSON() {
135 return this.toHex();
136 }
137 /**
138 * @description Encodes the value as a Uint8Array as per the parity-codec specifications
139 * @param isBare true when the value has none of the type-specific prefixes (internal)
140 * Period and phase are encoded:
141 * - The period of validity from the block hash found in the signing material.
142 * - The phase in the period that this transaction's lifetime begins (and, importantly,
143 * implies which block hash is included in the signature material). If the `period` is
144 * greater than 1 << 12, then it will be a factor of the times greater than 1<<12 that
145 * `period` is.
146 */
147 toU8a(_isBare) {
148 const period = this.period.toNumber();
149 const encoded = Math.min(15, Math.max(1, getTrailingZeros(period) - 1)) + ((this.phase.toNumber() / Math.max(period >> 12, 1)) << 4);
150 return new Uint8Array([
151 encoded & 0xff,
152 encoded >> 8
153 ]);
154 }
155 /**
156 * @description Get the block number of the start of the era whose properties this object describes that `current` belongs to.
157 */
158 birth(current) {
159 const phase = this.phase.toNumber();
160 const period = this.period.toNumber();
161 // FIXME No toNumber() here
162 return (~~((Math.max(bnToBn(current).toNumber(), phase) - phase) / period) * period) + phase;
163 }
164 /**
165 * @description Get the block number of the first block at which the era has ended.
166 */
167 death(current) {
168 // FIXME No toNumber() here
169 return this.birth(current) + this.period.toNumber();
170 }
171}
172/**
173 * @name GenericExtrinsicEra
174 * @description
175 * The era for an extrinsic, indicating either a mortal or immortal extrinsic
176 */
177export class GenericExtrinsicEra extends Enum {
178 constructor(registry, value) {
179 super(registry, {
180 ImmortalEra,
181 MortalEra
182 }, decodeExtrinsicEra(value));
183 }
184 /**
185 * @description Override the encoded length method
186 */
187 get encodedLength() {
188 return this.isImmortalEra
189 ? this.asImmortalEra.encodedLength
190 : this.asMortalEra.encodedLength;
191 }
192 /**
193 * @description Returns the item as a [[ImmortalEra]]
194 */
195 get asImmortalEra() {
196 if (!this.isImmortalEra) {
197 throw new Error(`Cannot convert '${this.type}' via asImmortalEra`);
198 }
199 return this.inner;
200 }
201 /**
202 * @description Returns the item as a [[MortalEra]]
203 */
204 get asMortalEra() {
205 if (!this.isMortalEra) {
206 throw new Error(`Cannot convert '${this.type}' via asMortalEra`);
207 }
208 return this.inner;
209 }
210 /**
211 * @description `true` if Immortal
212 */
213 get isImmortalEra() {
214 return this.index === 0;
215 }
216 /**
217 * @description `true` if Mortal
218 */
219 get isMortalEra() {
220 return this.index > 0;
221 }
222 /**
223 * @description Encodes the value as a Uint8Array as per the parity-codec specifications
224 * @param isBare true when the value has none of the type-specific prefixes (internal)
225 */
226 toU8a(isBare) {
227 return this.isMortalEra
228 ? this.asMortalEra.toU8a(isBare)
229 : this.asImmortalEra.toU8a(isBare);
230 }
231}