1 | import { Enum, Raw, Tuple, U64 } from '@polkadot/types-codec';
|
2 | import { bnToBn, formatNumber, hexToU8a, isHex, isObject, isU8a, u8aToBn, u8aToU8a } from '@polkadot/util';
|
3 | import { IMMORTAL_ERA } from './constants.js';
|
4 | function 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 |
|
13 | function 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 |
|
26 | function 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 |
|
36 | function 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 |
|
52 | function 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 |
|
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 |
|
82 |
|
83 |
|
84 |
|
85 | export class ImmortalEra extends Raw {
|
86 | constructor(registry, _value) {
|
87 |
|
88 |
|
89 | super(registry, IMMORTAL_ERA);
|
90 | }
|
91 | }
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 | export class MortalEra extends Tuple {
|
98 | constructor(registry, value) {
|
99 | super(registry, {
|
100 | period: U64,
|
101 | phase: U64
|
102 | }, decodeMortalEra(registry, value));
|
103 | }
|
104 | |
105 |
|
106 |
|
107 | get encodedLength() {
|
108 | return 2 | 0;
|
109 | }
|
110 | |
111 |
|
112 |
|
113 | get period() {
|
114 | return this[0];
|
115 | }
|
116 | |
117 |
|
118 |
|
119 | get phase() {
|
120 | return this[1];
|
121 | }
|
122 | |
123 |
|
124 |
|
125 | toHuman() {
|
126 | return {
|
127 | period: formatNumber(this.period),
|
128 | phase: formatNumber(this.phase)
|
129 | };
|
130 | }
|
131 | |
132 |
|
133 |
|
134 | toJSON() {
|
135 | return this.toHex();
|
136 | }
|
137 | |
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 |
|
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 |
|
157 |
|
158 | birth(current) {
|
159 | const phase = this.phase.toNumber();
|
160 | const period = this.period.toNumber();
|
161 |
|
162 | return (~~((Math.max(bnToBn(current).toNumber(), phase) - phase) / period) * period) + phase;
|
163 | }
|
164 | |
165 |
|
166 |
|
167 | death(current) {
|
168 |
|
169 | return this.birth(current) + this.period.toNumber();
|
170 | }
|
171 | }
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 | export class GenericExtrinsicEra extends Enum {
|
178 | constructor(registry, value) {
|
179 | super(registry, {
|
180 | ImmortalEra,
|
181 | MortalEra
|
182 | }, decodeExtrinsicEra(value));
|
183 | }
|
184 | |
185 |
|
186 |
|
187 | get encodedLength() {
|
188 | return this.isImmortalEra
|
189 | ? this.asImmortalEra.encodedLength
|
190 | : this.asMortalEra.encodedLength;
|
191 | }
|
192 | |
193 |
|
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 |
|
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 |
|
212 |
|
213 | get isImmortalEra() {
|
214 | return this.index === 0;
|
215 | }
|
216 | |
217 |
|
218 |
|
219 | get isMortalEra() {
|
220 | return this.index > 0;
|
221 | }
|
222 | |
223 |
|
224 |
|
225 |
|
226 | toU8a(isBare) {
|
227 | return this.isMortalEra
|
228 | ? this.asMortalEra.toU8a(isBare)
|
229 | : this.asImmortalEra.toU8a(isBare);
|
230 | }
|
231 | }
|