-
Notifications
You must be signed in to change notification settings - Fork 4
/
compact.ts
135 lines (122 loc) · 4.55 KB
/
compact.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import { Codec, CodecVisitor, createCodec, metadata, ScaleDecodeError, withMetadata } from "../common/mod.ts"
import { AnyCodec } from "../mod.ts"
import { constant } from "./constant.ts"
import { u128, u16, u256, u32, u64, u8 } from "./int.ts"
import { field, object } from "./object.ts"
import { tuple } from "./tuple.ts"
const MAX_U6 = 0b00111111
const MAX_U14 = 0b00111111_11111111
const MAX_U30 = 0b00111111_11111111_11111111_11111111
export const compactVisitor = new CodecVisitor<AnyCodec>()
export function compact<I, O>(codec: Codec<I, O>): Codec<I, O> {
return compactVisitor.visit(codec) as any
}
function compactNumber($base: Codec<number>): Codec<number> {
return createCodec({
_metadata: metadata("$.compact", compact, $base),
_staticSize: 5,
_encode(buffer, value) {
if (value <= MAX_U6) {
buffer.array[buffer.index++] = value << 2
} else if (value <= MAX_U14) {
u16._encode(buffer, (value << 2) | 0b01)
} else if (value <= MAX_U30) {
// Because JS bitwise ops use *signed* 32-bit ints, this operation
// produces negative values when `value >= 2 ** 29`. However, this is ok,
// as `setUint32` correctly casts these negative values back to unsigned
// 32-bit ints.
u32._encode(buffer, (value << 2) | 0b10)
} else {
buffer.array[buffer.index++] = 0b11
u32._encode(buffer, value)
}
},
_decode(buffer) {
switch (buffer.array[buffer.index]! & 0b11) {
case 0:
return buffer.array[buffer.index++]! >> 2
case 1:
return u16._decode(buffer) >> 2
case 2:
// We use an unsigned right shift, as the default shift operator
// uses signed 32-bit ints, which would yield invalid values.
return u32._decode(buffer) >>> 2
default:
if (buffer.array[buffer.index++]! !== 3) throw new ScaleDecodeError(this, buffer, "Out of range for U32")
return u32._decode(buffer)
}
},
_assert(assert) {
$base._assert(assert)
},
})
}
const compactU8 = compactNumber(u8)
const compactU16 = compactNumber(u16)
const compactU32 = compactNumber(u32)
compactVisitor.add(u8, () => compactU8)
compactVisitor.add(u16, () => compactU16)
compactVisitor.add(u32, () => compactU32)
function compactBigInt($base: Codec<bigint>): Codec<bigint> {
return createCodec({
_metadata: metadata("$.compact", compact, $base),
_staticSize: 5,
_encode(buffer, value) {
if (value <= 0xff_ff_ff_ff) {
compactU32._encode(buffer, Number(value))
return
}
let extraBytes = 0
let _value = value >> 32n
while (_value > 0n) {
_value >>= 8n
extraBytes++
}
buffer.array[buffer.index++] = (extraBytes << 2) | 0b11
u32._encode(buffer, Number(value & 0xff_ff_ff_ffn))
_value = value >> 32n
buffer.pushAlloc(extraBytes)
for (let i = 0; i < extraBytes; i++) {
buffer.array[buffer.index++] = Number(_value & 0xffn)
_value >>= 8n
}
buffer.popAlloc()
},
_decode(buffer) {
const b = buffer.array[buffer.index]!
if ((b & 0b11) < 3 || b === 3) {
return BigInt(compactU32._decode(buffer))
}
const extraBytes = b >> 2
buffer.index++
let value = BigInt(u32._decode(buffer))
for (let i = 0; i < extraBytes; i++) {
value |= BigInt(buffer.array[buffer.index++]!) << BigInt(32 + i * 8)
}
return value
},
_assert(assert) {
$base._assert(assert)
},
})
}
const compactU64 = compactBigInt(u64)
const compactU128 = compactBigInt(u128)
const compactU256 = compactBigInt(u256)
compactVisitor.add(u64, () => compactU64)
compactVisitor.add(u128, () => compactU128)
compactVisitor.add(u256, () => compactU256)
compactVisitor.add(constant<any>, (codec) => codec)
compactVisitor.add(tuple<any[]>, (codec, ...entries) => {
if (entries.length === 0) return codec
if (entries.length > 1) throw new Error("Cannot derive compact codec for tuples with more than one field")
return withMetadata(metadata("$.compact", compact<any, any>, codec), tuple(compact(entries[0]!)))
})
compactVisitor.add(field<any, any, any>, (codec, key, value) => {
return withMetadata(metadata("$.compact", compact, codec), field(key, compact(value)))
})
compactVisitor.add(object<any[]>, (codec, ...entries) => {
if (entries.length === 0) return codec
if (entries.length > 1) throw new Error("Cannot derive compact codec for objects with more than one field")
return withMetadata(metadata("$.compact", compact, codec), compact(entries[0]!))
})