Commit 27d8942e authored by Mark Tyneway's avatar Mark Tyneway

core-utils: update batch serialization

Update the batch serialization to allow for typed batches.
Also include logic for type 0 batches, which are compressed
with zlib.
parent 8ef006f7
---
'@eth-optimism/core-utils': patch
---
Update batch serialization with typed batches and zlib compression
...@@ -35,7 +35,9 @@ ...@@ -35,7 +35,9 @@
"@ethersproject/abstract-provider": "^5.5.1", "@ethersproject/abstract-provider": "^5.5.1",
"@ethersproject/bytes": "^5.5.0", "@ethersproject/bytes": "^5.5.0",
"@ethersproject/providers": "^5.5.3", "@ethersproject/providers": "^5.5.3",
"@ethersproject/transactions": "^5.5.0",
"@ethersproject/web": "^5.5.1", "@ethersproject/web": "^5.5.1",
"bufio": "^1.0.7",
"chai": "^4.3.4", "chai": "^4.3.4",
"ethers": "^5.5.4" "ethers": "^5.5.4"
}, },
......
declare module 'bufio' {
class BufferWriter {
public offset: number
constructor()
render(): Buffer
getSize(): number
seek(offset: number): this
destroy(): this
writeU8(n: number): this
writeU16(n: number): this
writeU16BE(n: number): this
writeU24(n: number): this
writeU24BE(n: number): this
writeU32(n: number): this
writeU32BE(n: number): this
writeU40(n: number): this
writeU40BE(n: number): this
writeU48(n: number): this
writeU48BE(n: number): this
writeU56(n: number): this
writeU56BE(n: number): this
writeU64(n: number): this
writeU64BE(n: number): this
writeBytes(b: Buffer): this
copy(value: number, start: number, end: number): this
}
class BufferReader {
constructor(data: Buffer, copy?: boolean)
getSize(): number
check(n: number): void
left(): number
seek(offset: number): this
start(): number
end(): number
destroy(): this
readU8(): number
readU16(): number
readU16BE(): number
readU24(): number
readU24BE(): number
readU32(): number
readU32BE(): number
readU40(): number
readU40BE(): number
readU48(): number
readU48BE(): number
readU56(): number
readU56BE(): number
readU64(): number
readU64BE(): number
readBytes(size: number, copy?: boolean): Buffer
}
class Struct {
constructor()
encode(extra?: object): Buffer
decode<T extends Struct>(data: Buffer, extra?: object): T
getSize(extra?: object): number
fromHex(s: string, extra?: object): this
toHex(): string
write(bw: BufferWriter, extra?: object): BufferWriter
read(br: BufferReader, extra?: object): this
static read<T extends Struct>(br: BufferReader, extra?: object): T
static decode<T extends Struct>(data: Buffer, extra?: object): T
static fromHex<T extends Struct>(s: string, extra?: object): T
}
}
import { BigNumber, ethers } from 'ethers' import zlib from 'zlib'
import { add0x, remove0x, encodeHex } from '../common' import { parse, serialize } from '@ethersproject/transactions'
import { ethers } from 'ethers'
import { Struct, BufferWriter, BufferReader } from 'bufio'
import { remove0x } from '../common'
export interface BatchContext { export interface BatchContext {
numSequencedTransactions: number numSequencedTransactions: number
...@@ -9,119 +13,381 @@ export interface BatchContext { ...@@ -9,119 +13,381 @@ export interface BatchContext {
blockNumber: number blockNumber: number
} }
export enum BatchType {
LEGACY = -1,
ZLIB = 0,
}
export interface AppendSequencerBatchParams { export interface AppendSequencerBatchParams {
shouldStartAtElement: number // 5 bytes -- starts at batch shouldStartAtElement: number // 5 bytes -- starts at batch
totalElementsToAppend: number // 3 bytes -- total_elements_to_append totalElementsToAppend: number // 3 bytes -- total_elements_to_append
contexts: BatchContext[] // total_elements[fixed_size[]] contexts: BatchContext[] // total_elements[fixed_size[]]
transactions: string[] // total_size_bytes[],total_size_bytes[] transactions: string[] // total_size_bytes[],total_size_bytes[]
type?: BatchType
} }
const APPEND_SEQUENCER_BATCH_METHOD_ID = 'appendSequencerBatch()' const APPEND_SEQUENCER_BATCH_METHOD_ID = 'appendSequencerBatch()'
const FOUR_BYTE_APPEND_SEQUENCER_BATCH = Buffer.from(
ethers.utils.id(APPEND_SEQUENCER_BATCH_METHOD_ID).slice(2, 10),
'hex'
)
// Legacy support
// This function returns the serialized batch
// without the 4 byte selector and without the
// 0x prefix
export const encodeAppendSequencerBatch = ( export const encodeAppendSequencerBatch = (
b: AppendSequencerBatchParams b: AppendSequencerBatchParams
): string => { ): string => {
const encodeShouldStartAtElement = encodeHex(b.shouldStartAtElement, 10) for (const tx of b.transactions) {
const encodedTotalElementsToAppend = encodeHex(b.totalElementsToAppend, 6) if (tx.length % 2 !== 0) {
const encodedContextsHeader = encodeHex(b.contexts.length, 6)
const encodedContexts =
encodedContextsHeader +
b.contexts.reduce((acc, cur) => acc + encodeBatchContext(cur), '')
const encodedTransactionData = b.transactions.reduce((acc, cur) => {
if (cur.length % 2 !== 0) {
throw new Error('Unexpected uneven hex string value!') throw new Error('Unexpected uneven hex string value!')
} }
const encodedTxDataHeader = remove0x( }
BigNumber.from(remove0x(cur).length / 2).toHexString() const batch = sequencerBatch.encode(b)
).padStart(6, '0') const fnSelector = batch.slice(2, 10)
return acc + encodedTxDataHeader + remove0x(cur) if (fnSelector !== FOUR_BYTE_APPEND_SEQUENCER_BATCH.toString('hex')) {
}, '') throw new Error(`Incorrect function signature`)
return ( }
encodeShouldStartAtElement + return batch.slice(10)
encodedTotalElementsToAppend +
encodedContexts +
encodedTransactionData
)
}
const encodeBatchContext = (context: BatchContext): string => {
return (
encodeHex(context.numSequencedTransactions, 6) +
encodeHex(context.numSubsequentQueueTransactions, 6) +
encodeHex(context.timestamp, 10) +
encodeHex(context.blockNumber, 10)
)
} }
// Legacy support
// This function assumes there is no 4byte selector
// as part of the input data
export const decodeAppendSequencerBatch = ( export const decodeAppendSequencerBatch = (
b: string b: string
): AppendSequencerBatchParams => { ): AppendSequencerBatchParams => {
b = remove0x(b) const calldata =
'0x' + FOUR_BYTE_APPEND_SEQUENCER_BATCH.toString('hex') + remove0x(b)
const shouldStartAtElement = b.slice(0, 10) return sequencerBatch.decode(calldata)
const totalElementsToAppend = b.slice(10, 16) }
const contextHeader = b.slice(16, 22)
const contextCount = parseInt(contextHeader, 16) // Legacy support
export const sequencerBatch = {
let offset = 22 encode: (params: AppendSequencerBatchParams): string => {
const contexts = [] const batch = new SequencerBatch({
for (let i = 0; i < contextCount; i++) { shouldStartAtElement: params.shouldStartAtElement,
const numSequencedTransactions = b.slice(offset, offset + 6) totalElementsToAppend: params.totalElementsToAppend,
offset += 6 contexts: params.contexts.map((c) => new Context(c)),
const numSubsequentQueueTransactions = b.slice(offset, offset + 6) transactions: params.transactions.map((t) =>
offset += 6 BatchedTx.fromTransaction(t)
const timestamp = b.slice(offset, offset + 10)
offset += 10
const blockNumber = b.slice(offset, offset + 10)
offset += 10
contexts.push({
numSequencedTransactions: parseInt(numSequencedTransactions, 16),
numSubsequentQueueTransactions: parseInt(
numSubsequentQueueTransactions,
16
), ),
timestamp: parseInt(timestamp, 16), type: params.type,
blockNumber: parseInt(blockNumber, 16),
}) })
return batch.toHex()
},
decode: (b: string): AppendSequencerBatchParams => {
const buf = Buffer.from(remove0x(b), 'hex')
const fnSelector = buf.slice(0, 4)
if (Buffer.compare(fnSelector, FOUR_BYTE_APPEND_SEQUENCER_BATCH) !== 0) {
throw new Error(`Incorrect function signature`)
} }
const transactions = [] const batch = SequencerBatch.decode<SequencerBatch>(buf)
for (const context of contexts) { const params: AppendSequencerBatchParams = {
for (let i = 0; i < context.numSequencedTransactions; i++) { shouldStartAtElement: batch.shouldStartAtElement,
const size = b.slice(offset, offset + 6) totalElementsToAppend: batch.totalElementsToAppend,
offset += 6 contexts: batch.contexts.map((c) => ({
const raw = b.slice(offset, offset + parseInt(size, 16) * 2) numSequencedTransactions: c.numSequencedTransactions,
transactions.push(add0x(raw)) numSubsequentQueueTransactions: c.numSubsequentQueueTransactions,
offset += raw.length timestamp: c.timestamp,
blockNumber: c.blockNumber,
})),
transactions: batch.transactions.map((t) => t.toHexTransaction()),
type: batch.type,
}
return params
},
}
export class Context extends Struct {
// 3 bytes
public numSequencedTransactions: number = 0
// 3 bytes
public numSubsequentQueueTransactions: number = 0
// 5 bytes
public timestamp: number = 0
// 5 bytes
public blockNumber: number = 0
constructor(options: Partial<Context> = {}) {
super()
if (typeof options.numSequencedTransactions === 'number') {
this.numSequencedTransactions = options.numSequencedTransactions
}
if (typeof options.numSubsequentQueueTransactions === 'number') {
this.numSubsequentQueueTransactions =
options.numSubsequentQueueTransactions
}
if (typeof options.timestamp === 'number') {
this.timestamp = options.timestamp
}
if (typeof options.blockNumber === 'number') {
this.blockNumber = options.blockNumber
} }
} }
return { getSize(): number {
shouldStartAtElement: parseInt(shouldStartAtElement, 16), return 16
totalElementsToAppend: parseInt(totalElementsToAppend, 16), }
contexts,
transactions, write(bw: BufferWriter): BufferWriter {
bw.writeU24BE(this.numSequencedTransactions)
bw.writeU24BE(this.numSubsequentQueueTransactions)
bw.writeU40BE(this.timestamp)
bw.writeU40BE(this.blockNumber)
return bw
}
read(br: BufferReader): this {
this.numSequencedTransactions = br.readU24BE()
this.numSubsequentQueueTransactions = br.readU24BE()
this.timestamp = br.readU40BE()
this.blockNumber = br.readU40BE()
return this
} }
} }
export const sequencerBatch = { // transaction
encode: (b: AppendSequencerBatchParams) => { export class BatchedTx extends Struct {
return ( // 3 bytes
ethers.utils.id(APPEND_SEQUENCER_BATCH_METHOD_ID).slice(0, 10) + public txSize: number
encodeAppendSequencerBatch(b) // rlp encoded transaction
) public raw: Buffer
public tx: ethers.Transaction
constructor(tx?: ethers.Transaction) {
super()
this.tx = tx
}
getSize(): number {
if (this.raw && this.raw.length) {
return this.raw.length + 3
}
const tx = serialize(
{
nonce: this.tx.nonce,
gasPrice: this.tx.gasPrice,
gasLimit: this.tx.gasLimit,
to: this.tx.to,
value: this.tx.value,
data: this.tx.data,
}, },
decode: (b: string): AppendSequencerBatchParams => { {
b = remove0x(b) v: this.tx.v,
const functionSelector = b.slice(0, 8) r: this.tx.r,
if ( s: this.tx.s,
functionSelector !== }
ethers.utils.id(APPEND_SEQUENCER_BATCH_METHOD_ID).slice(2, 10) )
) {
throw new Error('Incorrect function signature') // remove 0x prefix
} this.raw = Buffer.from(remove0x(tx), 'hex')
return decodeAppendSequencerBatch(b.slice(8)) return this.raw.length + 3
}
write(bw: BufferWriter): BufferWriter {
bw.writeU24BE(this.txSize)
bw.writeBytes(this.raw)
return bw
}
read(br: BufferReader): this {
this.txSize = br.readU24BE()
this.raw = br.readBytes(this.txSize)
return this
}
toTransaction(): ethers.Transaction {
if (this.tx) {
return this.tx
}
return parse(this.raw)
}
toHexTransaction(): string {
if (this.raw) {
return '0x' + this.raw.toString('hex')
}
return serialize(
{
nonce: this.tx.nonce,
gasPrice: this.tx.gasPrice,
gasLimit: this.tx.gasLimit,
to: this.tx.to,
value: this.tx.value,
data: this.tx.data,
}, },
{
v: this.tx.v,
r: this.tx.r,
s: this.tx.s,
}
)
}
// TODO: inconsistent API with toTransaction
// but unnecessary right now
// this should be fromHexTransaction
fromTransaction(tx: string): this {
this.raw = Buffer.from(remove0x(tx), 'hex')
this.txSize = this.raw.length
return this
}
fromHex(s: string, extra?: object): this {
const buffer = Buffer.from(remove0x(s), 'hex')
return this.decode(buffer, extra)
}
static fromTransaction(s: string) {
return new this().fromTransaction(s)
}
}
export class SequencerBatch extends Struct {
// 5 bytes
public shouldStartAtElement: number
// 3 bytes
public totalElementsToAppend: number
// 3 byte header for count, []Context
public contexts: Context[]
// []3 byte size, rlp encoded tx
public transactions: BatchedTx[]
// The batch type that determines how
// it is serialized
public type: BatchType
constructor(options: Partial<SequencerBatch> = {}) {
super()
this.contexts = []
this.transactions = []
if (typeof options.shouldStartAtElement === 'number') {
this.shouldStartAtElement = options.shouldStartAtElement
}
if (typeof options.totalElementsToAppend === 'number') {
this.totalElementsToAppend = options.totalElementsToAppend
}
if (Array.isArray(options.contexts)) {
this.contexts = options.contexts
}
if (Array.isArray(options.transactions)) {
this.transactions = options.transactions
}
if (typeof options.type === 'number') {
this.type = options.type
}
}
write(bw: BufferWriter): BufferWriter {
bw.writeBytes(FOUR_BYTE_APPEND_SEQUENCER_BATCH)
bw.writeU40BE(this.shouldStartAtElement)
bw.writeU24BE(this.totalElementsToAppend)
const contexts = this.contexts.slice()
if (this.type === BatchType.ZLIB) {
contexts.unshift(
new Context({
blockNumber: 0,
timestamp: 0,
numSequencedTransactions: 0,
numSubsequentQueueTransactions: 0,
})
)
}
bw.writeU24BE(contexts.length)
for (const context of contexts) {
context.write(bw)
}
if (this.type === BatchType.ZLIB) {
const writer = new BufferWriter()
for (const tx of this.transactions) {
tx.write(writer)
}
const compressed = zlib.deflateSync(writer.render())
bw.writeBytes(compressed)
} else {
// Legacy
for (const tx of this.transactions) {
tx.write(bw)
}
}
return bw
}
read(br: BufferReader): this {
const selector = br.readBytes(4)
if (Buffer.compare(selector, FOUR_BYTE_APPEND_SEQUENCER_BATCH) !== 0) {
br.seek(-4)
}
this.type = BatchType.LEGACY
this.shouldStartAtElement = br.readU40BE()
this.totalElementsToAppend = br.readU24BE()
const contexts = br.readU24BE()
for (let i = 0; i < contexts; i++) {
const context = Context.read<Context>(br)
this.contexts.push(context)
}
// handle typed batches
if (this.contexts.length > 0 && this.contexts[0].timestamp === 0) {
switch (this.contexts[0].blockNumber) {
case 0: {
this.type = BatchType.ZLIB
const bytes = br.readBytes(br.left())
const inflated = zlib.inflateSync(bytes)
br = new BufferReader(inflated)
// remove the dummy context
this.contexts = this.contexts.slice(1)
break
}
}
}
for (const context of this.contexts) {
for (let i = 0; i < context.numSequencedTransactions; i++) {
const tx = BatchedTx.read<BatchedTx>(br)
this.transactions.push(tx)
}
}
return this
}
getSize(): number {
if (this.type === BatchType.ZLIB) {
return -1
}
let size = 8 + 3 + 4
for (const context of this.contexts) {
size += context.getSize()
}
for (const tx of this.transactions) {
size += tx.getSize()
}
return size
}
fromHex(s: string, extra?: object): this {
const buffer = Buffer.from(remove0x(s), 'hex')
return this.decode(buffer, extra)
}
toHex(): string {
return '0x' + this.encode().toString('hex')
}
} }
...@@ -7,23 +7,31 @@ import { ...@@ -7,23 +7,31 @@ import {
encodeAppendSequencerBatch, encodeAppendSequencerBatch,
decodeAppendSequencerBatch, decodeAppendSequencerBatch,
sequencerBatch, sequencerBatch,
BatchType,
SequencerBatch,
} from '../src' } from '../src'
describe('BatchEncoder', () => { describe('BatchEncoder', function () {
this.timeout(10_000)
// eslint-disable-next-line @typescript-eslint/no-var-requires
const data = require('./fixtures/calldata.json')
describe('appendSequencerBatch', () => { describe('appendSequencerBatch', () => {
it('should work with the simple case', () => { it('legacy: should work with the simple case', () => {
const batch = { const batch = {
shouldStartAtElement: 0, shouldStartAtElement: 0,
totalElementsToAppend: 0, totalElementsToAppend: 0,
contexts: [], contexts: [],
transactions: [], transactions: [],
type: BatchType.LEGACY,
} }
const encoded = encodeAppendSequencerBatch(batch) const encoded = encodeAppendSequencerBatch(batch)
const decoded = decodeAppendSequencerBatch(encoded) const decoded = decodeAppendSequencerBatch(encoded)
expect(decoded).to.deep.equal(batch) expect(decoded).to.deep.equal(batch)
}) })
it('should work with more complex case', () => { it('legacy: should work with more complex case', () => {
const batch = { const batch = {
shouldStartAtElement: 10, shouldStartAtElement: 10,
totalElementsToAppend: 1, totalElementsToAppend: 1,
...@@ -36,19 +44,57 @@ describe('BatchEncoder', () => { ...@@ -36,19 +44,57 @@ describe('BatchEncoder', () => {
}, },
], ],
transactions: ['0x45423400000011', '0x45423400000012'], transactions: ['0x45423400000011', '0x45423400000012'],
type: BatchType.LEGACY,
} }
const encoded = encodeAppendSequencerBatch(batch) const encoded = encodeAppendSequencerBatch(batch)
const decoded = decodeAppendSequencerBatch(encoded) const decoded = decodeAppendSequencerBatch(encoded)
expect(decoded).to.deep.equal(batch) expect(decoded).to.deep.equal(batch)
}) })
it('should work with mainnet calldata', () => { describe('mainnet data', () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires for (const [hash, calldata] of Object.entries(data)) {
const data = require('./fixtures/appendSequencerBatch.json') // Deserialize the raw calldata
for (const calldata of data.calldata) { const decoded = SequencerBatch.fromHex<SequencerBatch>(
const decoded = sequencerBatch.decode(calldata) calldata as string
const encoded = sequencerBatch.encode(decoded) )
expect(encoded).to.equal(calldata)
it(`${hash}`, () => {
const encoded = decoded.toHex()
expect(encoded).to.deep.equal(calldata)
const batch = SequencerBatch.decode(decoded.encode())
expect(decoded).to.deep.eq(batch)
})
it(`${hash} (compressed)`, () => {
// Set the batch type to be zlib so that the batch
// is compressed
decoded.type = BatchType.ZLIB
// Encode a compressed batch
const encodedCompressed = decoded.encode()
// Decode a compressed batch
const decodedPostCompressed =
SequencerBatch.decode<SequencerBatch>(encodedCompressed)
// Expect that the batch type is detected
expect(decodedPostCompressed.type).to.eq(BatchType.ZLIB)
// Expect that the contexts match
expect(decoded.contexts).to.deep.equal(decodedPostCompressed.contexts)
for (const [i, tx] of decoded.transactions.entries()) {
const got = decodedPostCompressed.transactions[i]
expect(got).to.deep.eq(tx)
}
// Reserialize the batch as legacy
decodedPostCompressed.type = BatchType.LEGACY
// Ensure that the original data can be recovered
const encoded = decodedPostCompressed.toHex()
expect(encoded).to.deep.equal(calldata)
})
it(`${hash}: serialize txs`, () => {
for (const tx of decoded.transactions) {
tx.toTransaction()
}
})
} }
}) })
......
{
"calldata": [
"0xd0f893440000011fcd00006500000400001900000000603e47620000b67be400000c00000200603e4aef0000b67c2d00003c00000000603e4c020000b67c4a00000200000000603e4f870000b67c9700006301d8914345750baf3939d57f2502c481154979e99b6049e5db5ae3d164376c2c543a5b75aabd10a41dd6305486b3b0a253b5cf247e0615370d7100c13f550d0ccd0189543f0000000000448700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00014300eabe2f5db7a62275de799b9aee03f55f01743563bbee17e722cd2efb0b1fbc1a246ba5723b499d41bd46f7c20c5eab5216e7b5adac96043c2d671de2b47588200089543f000000005ec2631e93a0fb06b5ec6d52c0a2d89a3f9672d6ba64bfa005ce000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000603e49750000000000000000000000000000000000000000000000000000000000000001534e58000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000013ee5cec085b3800000006301f1fc332c1562c12b6224e441f25a10d661e009adabce0a29e41896a85323bdaf6afac2438882c57aaa9e5f137e0365ecc27d27b3a73a108957c6ed4ee5bff8530189543f00000000000d8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063017f8f5cdac49108109ad45484957fb00202937447785218f4674332cf42fc6f3e458acda19fa8bd5f14b3209dd235667096cadc57c2c65330b20c6f6db7806a470189543f0000000000318700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000143008d4e5a4defd67ce253e0232e10a592857b6d04b0be36abe1fe4cefa24987df124b96b5840e08d70c536c1a49e28d767289c388b6cfade572989e74a4f28b36660089543f000000005ec3631e93a0fb06b5ec6d52c0a2d89a3f9672d6ba64bfa005ce000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000603e49d40000000000000000000000000000000000000000000000000000000000000001534e580000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000141371c5e0331000000006301bcd1432ea3305683f046d921354633808c8a9175fc9b248146b721f4261749d962c7bd3c3b7412d3f8714faf932cd9b44da135b645e8c1d2bb3c44ddbde358910189543f0000000000798700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630169a529e91ff78552b8289c3e42e42a4b69ed57e87e0d3a8739058b34ad1423eb68545b3c1a89683f0b110c66fe0771e0701d24ccdb31de1fef9307b9b3fb7d3401493e0000000000007a4a16a42407aa491564643e1dfc1fd50af29794efd294f093000063013172648acf5ea4cff6ea4893d30aa73983f67773a9ce7084a40e07dccd61cecd0833133b648af4df5ba168234583bc1214eca2687901e6ee71e23fbc42059ad00089543f00000000029c8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630195447e28a93f2dd4cbb2e00f71011ac7e938d280909be4a029054521bc348aae7c226ab01aaec7c2a00880e57ffb25197d05bf0be7c314a91383844824373c6c0089543f0000000000028700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063008070b45c42792670949cbae7e60278d2cd809c9347f26b976a3fc21b3757331904afa0cda74e27122ee02098e26504173be953a608fa01e02b124020e5639d54007a11ff0000000000bdd85eafa37734e4ad237c3a3443d64dc94ae998e7af086c7e000083019cedf37f33c099a4b81e0dc68c4b8d23a1150624d5cca9b938d4aa3c21aaef8c17ff865e8605486a0ecec67b685f1ae38f3c0158e19ed23a986363ccd2ccdb0f0189543f00000000002a8700daec35af8ff88c16bdf0418774cb3d7599b48a2900140000000000000000000000000000000000000000000000006f05b59d3b20000000006301165d2c69ba74b1b1ee893303b60c48797f883efbcb2019762feb26a9257bea9e7019c339f05e311e79d4799f2bfde302b7c0f82b53ef5b5c9e837a6441e877e00189543f0000000000188700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301d539deb29e2e26049c9168eca08fff60ecf63daf9cedaf27a01d95f6f8b47624185f18f727d156b6ecb4cc3dc75ad29756a1f0447d5c3ab638fd734dd090c9120089543f0000000000258700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630125e4deda3a4e62f65113248e1b3b8a834e6c2d291cedfb71a21e544c3c3217d06ed5d15f56cace11db5e0120e0d5f67a09ca9084cde7728991827851e753b14d0189543f0000000000108700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000143009d2d26177c80858fdba6683fa5d095dacb9634e9deb842286ec79c17d85f0d7b70c617d7878dded6bf0a7ca7fc1371e8bced146f6e7d0da215f67e9e0f92940c0189543f000000005ec4631e93a0fb06b5ec6d52c0a2d89a3f9672d6ba64bfa005ce000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000603e4ae20000000000000000000000000000000000000000000000000000000000000001534e580000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000143687087b5b40000000083018e22501ff1c962656f4bfbdab7479cc4ed8c91486c4b19150d03782d7f374467036d7cd2cdba105a4d916dfea70fb8cc2c60121f3baca85c6f2a48909c34d3780189543f00000000001a8700daec35af8ff88c16bdf0418774cb3d7599b48a2900140000000000000000000000000000000000000000000000008ac7230489e8000000006301019753a47c457cd6cad98373b2092f11a1aea45e9bdd27ab1105609b4c8f87453121c425f6864b1b85910b4c0b82743287f61bc210a46de870b484c07b17cd310189543f0000000000038700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301d08818d883a43fc9f5f64623dca45128884b782fef41d513efa33d4d40139b467641ed4c17071fb9c2bd045023307ed0b23a3fcbbb2f71e81b37e3a7aba1c6560089543f0000000000058700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301c90fa6273070d9ae5af8eea1f687ae507410a258c7861e726d2cf943e00b456f2b3bf75861fe705039179eda8db6ea2781480afd89a98e133f1fe53e379e1eda0189543f00000000000b8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063018b5913d83c37ddc2564db791692072efd3dc8e5fe10584ed96451778789131a14efe012befc05c4ad479a46ea57c7e48f7f64dd7b7c175a48dd445215b2ffa170189543f0000000000418700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000083019d241891335c51ad2cc632ed85dd94ab8eee00da71cd812ea1c40fbcb9c441f56e2f5a7f83e9aa02ae614ee89a4abd10c4332be95a1335be44d5cff6356a903a0089543f0000000000148700daec35af8ff88c16bdf0418774cb3d7599b48a2900140000000000000000000000000000000000000000000000013f306a2409fc0000000063018dc9bf7f0c853dc0884f0070f8a0413a83f0d9d4d8e5f6f1325deb65684f3485152071dfdeef0a933691bafc850265ccd3624ed00671f68e9cea9bb5b2575c6f01493e000000000000064a16a42407aa491564643e1dfc1fd50af29794efd294f0930000830148b78f666b6d633554897e96f80bb797908467619895cd83eaef7f10b84415d3060967fb0afd49eec5d6759aa9f92c4a5a356369b518210b95c93a0a89ca81e60189543f0000000000088700daec35af8ff88c16bdf0418774cb3d7599b48a2900140000000000000000000000000000000000000000000000068155a43676e000000000630122d546e170dee47a7952b7c158aacbc38b035f7e183e90a74e7ab91746dbf2bc5aaee131243eedd4781b970b3dd7cc40f6a2bb5fa15684d91e61bf38002c7e9d0189543f0000000000358700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000083016f747cffb6ad3bfaec6d17404d49502ec4bbace678deceb0a6d288dfe353e22b794bb94612e7016f8abf6d15186a3d737536d84f7b43be2ce552a4316280154a0089543f0000000000258700daec35af8ff88c16bdf0418774cb3d7599b48a2900140000000000000000000000000000000000000000000000c40eb7864285bedec00000630124b164e81e73327bab1cfa0ec9b73e6dc492b4078dcfafb66c11a01134705a9f0d0da528387e2ad59cb192ac51fa9c7b6ebee791e30b27fbf05b956d1c5bc4f20089543f0000000000158700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063011226070ed0b7b181dce226d2ac772265b9831fb462e54a040bd2644ea5092dda6e468fff3a6701d19b413086fb37919617e6bff18a0d9b0791f4bf373d1fbce10089543f0000000000108700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301db43b348b8b2008a01869382c9a91d50ece8adbc0f3a7b2e20b63d55f5db1088624cbc9eb64bcee9508a2352e8e7b03d226c7f06752bccc322bf3af7bc6b68d40189543f0000000000428700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063011a496ca06b19d3b6673c701ae9633e0a986edab74d9fb5b90c9840d129c0ab0967a424474451adb879fe40f49db3ff3505201a5136f88d93a486dffc95a8bbfb0089543f00000000000c8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00008301fbc6c4f4c92ca11aa750c91cd1ee111d69eee808eae4283931a27860599be53344f9763d439d3054e0dbf90667cb801320e802e74ac409610e25c1516caec4ae0089543f0000000000128700daec35af8ff88c16bdf0418774cb3d7599b48a29001400000000000000000000000000000000000000000000001043561a882930000000006301fdeb20b52896ceb6392d4d2f4514cbc187587990d736539a07ca5f59ee697ba84443d3b91894442e03741b67a76afe25ff709e0d318276b404abfc499071b3810189543f0000000000068700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301ebd8f5fda3ce757e953430c5d8c1ad689f2785c7c32f3b607410bd2a1955c3c1228a6bb7dced4e85680ab1d9e2a228c9b0a75114ff636aacd0dbae447e113eb90189543f00000000003f8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301837692935ae091a54c121aaf663f22e86b5c757c1c769ca2be3120f8d20f035a5d6ea340bf095f6a60c36e1e38fb975ad6d895723999abebe38b429637be73e00089543f0000000000208700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00014300bb50bf8b5c9c56a8b515110b42c4013f4e99cf9fcf6874c32e881554562265fa13a1539b789d0364d1e82e7587aad4adb894f4f3320d3517bd90bb08394d42640089543f000000005ec5631e93a0fb06b5ec6d52c0a2d89a3f9672d6ba64bfa005ce000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000603e4cfa0000000000000000000000000000000000000000000000000000000000000001534e580000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000143bdb4672850000000006301f96d41806e43f65d8570be44e855870bd2d9b78b512e61264b17105a22267e6338984c7bee80abb6a93f1c4680ae79bc8306cf8598e86d1d25126ce53866c1a10089543f0000000000ac8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630131b960ebca035ef79df2a1e63acdcd5d6e27789b2eb538f9f8232169165473216c51e7febcefc39ca0ed2ab56a316676bff81e5a051651767e0c1fb23588829a00493e0000000000000f4a16a42407aa491564643e1dfc1fd50af29794efd294f093000063015643222807be2acb62072275ed4652806827ecbe9780cf68af1d79a075135c7308230547dd63b8fee254f0a48fae06c7f74b9b1b7c9efa4b6c6801c8d3e98b940089543f0000000000368700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301ee5b0c65075609b827be37d236859f440e182f6c56b3e10736e04f26fab8bf993d3dee5160eb8e6845cbd7198f2afac45c41e93476f9e5b2b401bd1fd6085ad40089543f0000000000018700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301d13c1423797c1e5541f4e730ba701bb6e46bdaa1985cefca4129cb2928baae6c24634a3d4d58d705e65cb6eba169e82ef765a396cc808c273dd4cfa74e37ea680089543f0000000000108700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063019af7bc274b54c2434832f9714315c03020e4465bd283a4b3b25236fa308b5132447f528bae32614e2526c060df7b1139b67fd461b8c0fb688d0517fdc23826ed0189543f00000000000c8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630166b80cd6e820bd1ed6cb92d5a4c0bf178d4078666c1c1a6576f7bdeca1dd3ff40fdbf763431560cf1490d900aff9a42a7b1b0c53c16f93b56039dbc4059de7980089543f0000000000148700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063019eb6f4205fb5ee8009209763b2ee373fcad19b04e9b4411ec7393403e2a815d43b5453d8ff70c0b60ef45917c4789c6afc45c8955cd59432abc2b32bc2c37b5d01493e000000000000024a16a42407aa491564643e1dfc1fd50af29794efd294f09300008301d22b7b46b935bbe378ac97d58bbe84f8b57d9b240f56e9335ed8134214a480c032b15645d64f624cd707628971ece2c06366fd2bbff50cbb40891fe1cf0de2250189543f0000000000098700daec35af8ff88c16bdf0418774cb3d7599b48a29001400000000000000000000000000000000000000000000001043561a882930000000008301f3ecf58a1113fb3387131db1ea3feeb9e2f46e3e48ee1755a1171d3ebcde8ce12d35af50f607a050a62b614074a70d3101211de5f0e40bccdbb49a7206d049ad0089543f0000000000018700daec35af8ff88c16bdf0418774cb3d7599b4295da87d0000000000000000000000000000000000000000000000008ac7230489e800000000630188270db66b963f84c9707597b0c8b7b353042bf61585be66b653a97ead1bbf6a4c373e6831c72dae73ac1a9a6a4e20133f92f57815f0d5a3673d31ae8e7aa0030089543f0000000000168700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301bf64ddfdac64b87045ee2b616ff27224598cd3f74704abdf729f11a5c196436e43b5e9e7fca7b0fa3c3fe149ebf561f14342cfc7c08781a3c85b6cdfa574e0a40089543f0000000000208700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063010beb607a64dcab788fe6c6ad52674b82d41eb785fab9c3c7de72ad96472e9b5c094b2c6b1fe1ff3a4e03a0ed1f0a1a56c9fd10776c61062aa80352c9bc524a240189543f0000000000218700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630152df9af9c07cec85267c8d281bc35087dec67370356cfb4fdcf2afeb322082ad33376cb37f77411963298ba7d127a04236a3d00fe1feb8270c3a42fb090bbe210089543f00000000001b8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00008301244318f4f681328908180b145ca3467846f5cdc40fe6e9ada54f2e7dba843ee414b22618c62cd57adbbc86c5ac079bc69f7e45e87108c53f22ff619501a773be0089543f0000000000108700daec35af8ff88c16bdf0418774cb3d7599b48a29001400000000000000000000000000000000000000000000000340aad21b3b700000000063016715ac2a06087215c0d619320bbbe1f25e9643ec2b7a4dad9813875832360b33592babf1ed961d1c20fd6c9334fc35214ccd20a168c5d53df0b3cfcd40ce46f40089543f0000000000168700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301e3f78d50a6720d493515f979f3242514d8de70a93d4d00caa62b3991fc48e1811c7000a1cec9a13cb59a3bd639a346d6a5834f8f6bfc0b4896eb9f12f52a67750089543f00000000001d8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063010d7a3ee143952287c195738b1a238e88fe1cd7c721339f21a2c9fdab99352d862ad25cb00e9d28afad4de9a1c21d8bf16fbc09d47483c928343bd12d90cf0e940089543f0000000000458700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301675ad48eb9a9cb237be82eca75f0f058a53fc42e5952a1009e51523e3e7403ca2bc2086ee05a853fe839bfe3c8123c59373066885185f052c9d6835a6c20d9770189543f0000000000188700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301d104e95267f9651e269ef7ea3a5f0a99c044a0be82bf883c536102918dffed0e38e0e7635046abf595ad5fb1860dc3a13e858bbe1e053436ebf694b7d478f1c00189543f00000000000f8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630170935022f25655a71178c3439cdb3cdc7c2f9857fb65ee2c3af721997e849c55584d650cb4fb8b15f56b708828470224802f1ec9d7fd1afe2f0ad399c5e38ea40189543f00000000004e8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301c5bbd5a56cf2621d47a4acfe87b993fe625218e9b331ab25ea7b07005a40520d70ef48e060693a69c1dcb23300d407658551bcdc270da2a8cdacc91d5cfe25760089543f00000000001d8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00014300ff75b419c212ab48019e879247e2aad3a917dc6000e4c0a788af865637b2d8ac422cd67f43ce27c0d0d46c32cdc80409a74029d00c41ab9579adf760671cfd370089543f000000005ec6631e93a0fb06b5ec6d52c0a2d89a3f9672d6ba64bfa005ce000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000603e4e9c0000000000000000000000000000000000000000000000000000000000000001534e58000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000013f1070b03f0180000000630194b0adafa5ed2c77b5b500b13df63ba79d971f383e6716b58df912738766e33f01c11739fd39045d7f2ca3a6ff582391d4094c8910f683a4525b087f5fc5d49a0189543f0000000000a28700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063013a0f2024f85cd3038d8f565ef3e3a7f1ed920bf2340621f1c961c3cf2551a21d234a8d69ab9127dffc3193d3a6af98e5d5f70e166121094ad51d91c93e9e938d0089543f0000000000288700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006300f322135adf6c6959bc012fe5ffb14bb8da6443a80f4e85799ca645ffc369eb09155f05bd65a64741cf089427597a1c35130428c2ecef60dad2b3f1f6da63e3c0007a11ff0000000000bed85eafa37734e4ad237c3a3443d64dc94ae998e7af086c7e00008301ff3ecc7ecf5a169c2b1c4360c63dcf9a8d786d804f400f1bd4bb0fcb9401644f618ca9780aae23bab8a688cf6bf40258e0c44b8089e6bf0989d5aafe9236d3fa0089543f0000000000158700daec35af8ff88c16bdf0418774cb3d7599b48a2900140000000000000000000000000000000000000000000000001bc16d674ec800000000630142e1b6d027dd1fb9cf43dbb834aaa03547ec356a65a693502546416bd8596250570285bcce68e72ce1bbd7fd3f1b3f8713d06e2b854eb718cafd1fbe3b2ea8ba0089543f0000000000138700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301f343a551e25a7254de23e818c649c30cf974b4f1e7feef4c30eab697f62e71a809006682008e5c25b1816228ce36d77e503e6ce1bdfe3027c6d29787be3ae1330089543f0000000000138700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301bf45e708b0d48a939480d9a16994e7f9b325b55d3a1faa989abca22ec877c799269be034bb0d4dc36a2f684295057e1f1a7de8577114fc660bbe37edbf16f84c0089543f00000000001a8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00008301f4cc4215e75d412821ef74ff9c20918c421227217f7c135f9b56a5bab8411f7061a8b99a11ea37c5346f62e70521f60ed7e664fb4b0f82e4c6a1d3e62c33607c0089543f0000000000048700daec35af8ff88c16bdf0418774cb3d7599b48a290014000000000000000000000000000000000000000000000001158e460913d00000000063013bd03efc62589e24d9d53547a31e18a18e24cb4c820ddbdf2cb3c20323d19ea01ebc043698a7a80ddc632682461ff9cadb95b19ec520f7528dea5bc57017d6df0089543f00000000001a8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301ab3f7535024aaf1bbadd31d33aeedfaf3325d617e8b3b7814080802dd4fe79d55b40120b9f68ece14e45bb347841bd2bad35c0e93f43da8ccf8c0857e4eb1fee0189543f0000000000208700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630145d68cab27031deb7b55e2e6df0df2629c3f07a60a09f433bbd45cd3594855f8508c5ab1201189091b8197d082a7d62abc6e4a28570ef28cfab8bcaf42679a8b0089543f0000000000058700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063013615d80a47143ea2f0102f196e6a2feda49b3be3352c88bb2dec2a9a567e22632fb5097f949412b398ca857b2f105b0f032a69be0f8bd401174a29ae67a8da5a0189543f0000000000148700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630128b5fef41c92369c84e8e742df1253f6eb624cfe5c807de5cb49869a7eaf573416ba55a9f86034b35a11f0c158af9ef07640e71cbcb093cb03a38b75d427ca360089543f0000000000058700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301fc2fc4d368f9f3ef73d9a4114c4a61116f7fd6259c4f172599511882a874993b437960110af8633fd8727372a880194569d295862c321dd1cfd961394e6c21eb0089543f0000000000b48700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301b2c09b2cda9dca2a6c7441d40e24d2a7b481ff76b7759d2a2aebdd7b1b2b3e9d551638ba91ae1511d03be6c90e13d8a931cb46ba48edf1f3c972c2bc6c4b30a50189543f0000000000428700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301ea34d378265585e18e8bac13f67c9733a4055886ddabbd6e467396d964c3dea6361f61256ec3eb9658c88fb1333ae4e74343622e18573a1545ca38af763a4ec40089543f0000000000348700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301563eed133b104dc9d7430fe5082fa1b722b14236053ba0003bff64fb52f073ae6c0c3985fc59c43bc799868fc2222410131a5f08b01dca6bdfc33e9df01c48900089543f0000000000028700daec35af8ff88c16bdf0418774cb3d7599b49741fb2200006301f8773e89f4266cb5afc584dc3e10b4a52b7fefa672ca7f627f758810ed1577ec56cd9dc44571c53fae24ecccd19907791eee25d5e0dd058e5444b02ad91836300089543f0000000000078700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063019ac5f89805eb37c5089ceb5f22bcba2abe7e5da0085adb520d0c2ee4efdf3cab3533f854c08f23f4071c42270b9a074b14bba44a2027e382b2dc0377621fd31100493e000000000000034a16a42407aa491564643e1dfc1fd50af29794efd294f0930000630135234c82daed5f5bb355d7f3f81edc0f5f9f0d4d11365d4e948bd2c02e7031cc291ca1f5c76d8ebe5c1a930a9375acf73f2343cc860aa0633643aecf7420eeb00089543f00000000001d8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0001430051884bdaf68fe6c64943a5f3160b5fcd7ef9140463937fc8d1ecdb3c916d7096354237eb3c63430116601c5b887dc44a4d4bce5639be1fa36b91243bb76e2b350189543f000000005ec7631e93a0fb06b5ec6d52c0a2d89a3f9672d6ba64bfa005ce000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000603e508c0000000000000000000000000000000000000000000000000000000000000001534e58000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000013cf0dfffc45f0000000063016e3845ed8e5ae218069cfa7ca98d3ffd86505d0383a3655f8cd46c557bc399c50c81cdfef32719125e7b84b1fd4e7c7535bf5a464a23a86fd9801b146b700d8f0189543f00000000000d8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063014c626ef869f07ea4cee4520d5bd24f2c2772a42ce289dfaedaed255446b03c0d2abca85ccfe8233061169c0fcd12d740750f0b65dd707dfe665d4d9cfaf757230189543f00000000000c8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301a0d59339cea6846e08148f7d9daba52402649546b5875c594f2f81209b36794a3e310fa64c0563d53334ae6fe87762426142dfa75b31f1021dd4092f4be895e80089543f00000000002f8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063014bd68c44229ea3cd8c4ff073a9cedbb5ff4387b257b29ce0e18ace691cd97677406b528411c6cd65df52a670dccc257bf290abd1805ba551f151fbd6355bdb980189543f0000000000068700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063016e7f6fe17cb0d1ed5970c49cc0745e3cae90e8e5a2004217472db6a1a1b9ab1124797d03a271feb38e66e690453904f568e61e6623900299f3b8079027fa15570189543f00000000002e8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063015702ed8c76f782970f0579f74a02931fb3e9dca6f3ba750d97d0df7ae60d3d10221ad66f898704bc9987b54d1109da9735c8d7acf2e4a25668134d641ac2d9120089543f00000000000e8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630129bd1bc30f7bfeb956ae7725bcd7144bc42291caf0628d7c8fb75a103f57fd6d4ce006acba63dd5593603368a0b154197d0826b8f9c3d23b93d72dc830c48a0e0189543f00000000001d8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000083019cbadc64fb19e3e912ffc2492bf42130d21fb67e4654c11551f3c3dd28e4ded951eae21b5b7f98a7f1e27fdf30fce409363823c97ba03643cbd7e65b964432a90089543f0000000000008700daec35af8ff88c16bdf0418774cb3d7599b48a29001400000000000000000000000000000000000000000000000b38b3bb4459dc00000000630198e2a81ec1c025dc5bcec824708cc1f264134aa6121d90e7c284c77907ee0d0845b88fd9c030bc20002dcbf5a6709af1c42ba4c8ad6e84a55e3e48a1c79974160089543f00000000005b8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063014cc92f396e2790dd5ab926c2e4c1be43000b1098fe7eb2822e0b49ea5ea076f01fb68a84640524d5c525bf989c4a9aff9a02750a81c535c031a8452aed300f420189543f0000000000218700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063019af9087760f24d93e0fbde3bd0e2f2e64fb8d8c36d024fd6cb4a58c34000b2e810e39efa69c5310be3580a4d94bbf069487d65172dc8d39d9bcb75261fd841560189543f0000000000458700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063015402786088ba83e4a68e5f05a7e9c8089c862bfd9c134b9a9ee38aed4a39a08d12d4bf29039655440937a990e14d699101d883246a29b62977aeb9a0e8252f6d0089543f0000000000178700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301bca73ae8381f33c7ec9e0f6a5b518f12854b0061e63a805b3d5941c1516128ce0775f6819e673108d4654a2018289aa951f9340dbee582b9c6b548ec6450149e0189543f0000000000828700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063011bcab33ce1cd156607d9c5a4cefa0d67b82c89462b0d3d4cfd79fa7b2945ee0c14103722c3702f1e247190a3baf4329fd0867d9ceff2bf212e9ff633a89915010089543f00000000001c8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e00006301b9ca39e85b19a0f5f03b067f2bca5be6dc27294107dd4e9ffa9c1af204b6feb235c7e21a385cc3fad8ddfa3aefe7951e2494d63f0a9f057923b39a73008e2a250189543f0000000000388700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063013656758d6e0c9219bb9de64106bb2dcaaec309bc54c0e53993cd37cd9cd03bb15b5102cbef23c0f2e60fcd16e8729059bc6f01546f8dd2c94ee4c9cdae79a0040089543f00000000003f8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630157b7af6659df3a6f211042201fefbaf98780f3b2945398494147dd0a08add01a37fb4cdbccd4df5949381357110e41daf963859999bde4cee42291dadb4fd31b0089543f00000000000d8700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000063012a0b8faa1536ed92b8c2eef51fd0e8a5a82d4a7ada7f2a46ed8e6b6ec29e9a263bd1866f30953a0ceefd68c4a931975bf487ff35fed2b6b3837cc83f0b5486510189543f0000000000218700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e000083012aa213c2eaa5a3f0887ba7ab48498bd289bc54a9e59fbff3a8a2a13e13eb9598399c58f16b07789e8c36268985d107091b01fe0afa3faa6fd71baaeef68afb340089543f00000000001f8700daec35af8ff88c16bdf0418774cb3d7599b48a2900140000000000000000000000000000000000000000000000056bc75e2d6310000000006301da0581ee658208bd90e7e1cc34873f56a3395523a5bdb7dcc1b3245b3cf2b4ad2fd8f550641f2ec4700865d040583e8ff7297dc21f06821332bd5e1aa01ba2770089543f0000000000468700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e0000630114adc64102322203225ec04c16480aa2d7b73e323f4ca68256753726fb81e4450f4318ca276d297a402371994ced771b50eb7409744c8f9182d01599e9c5c6ba0189543f0000000000158700daec35af8ff88c16bdf0418774cb3d7599b4af086c7e"
]
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{ {
"extends": "../../tsconfig.json" "extends": "../../tsconfig.json",
"typeRoots": ["node_modules/@types", "src/@types"]
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment