diff --git a/.github/workflows/hugo.yaml b/.github/workflows/hugo.yaml new file mode 100644 index 00000000..e57968d8 --- /dev/null +++ b/.github/workflows/hugo.yaml @@ -0,0 +1,78 @@ +# Sample workflow for building and deploying a Hugo site to GitHub Pages +name: Deploy Hugo site to Pages + +on: + # Runs on pushes targeting the gh-pages branch + push: + branches: + - gh-pages + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +# Default to bash +defaults: + run: + shell: bash + +jobs: + # Build job + build: + runs-on: ubuntu-latest + env: + HUGO_VERSION: 0.111.3 + steps: + - name: Install Hugo CLI + run: | + wget -O ${{ runner.temp }}/hugo.deb https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.deb \ + && sudo dpkg -i ${{ runner.temp }}/hugo.deb + - name: Install Dart Sass Embedded + run: sudo snap install dart-sass-embedded + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 0 + - name: Setup Pages + id: pages + uses: actions/configure-pages@v3 + - name: Install Node.js dependencies + run: "[[ -f package-lock.json || -f npm-shrinkwrap.json ]] && npm ci || true" + - name: Build with Hugo + env: + # For maximum backward compatibility with Hugo modules + HUGO_ENVIRONMENT: production + HUGO_ENV: production + run: | + hugo \ + --gc \ + --minify \ + --baseURL "${{ steps.pages.outputs.base_url }}/" + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: ./public + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 \ No newline at end of file diff --git a/archetypes/default.md b/archetypes/default.md new file mode 100644 index 00000000..00e77bd7 --- /dev/null +++ b/archetypes/default.md @@ -0,0 +1,6 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +date: {{ .Date }} +draft: true +--- + diff --git a/assets/.DS_Store b/assets/.DS_Store new file mode 100644 index 00000000..5008ddfc Binary files /dev/null and b/assets/.DS_Store differ diff --git a/config.toml b/config.toml new file mode 100644 index 00000000..177a70dc --- /dev/null +++ b/config.toml @@ -0,0 +1,14 @@ +baseURL = 'https://paritytech.github.io/parity-scale-codec/' +languageCode = 'en-us' +title = 'SCALE' +theme = 'hugo-book-master' +enableGitInfo = true +[params] + BookLogo = 'parity-logo-square.png' + BookSection = '*' +[markup] + [markup.tableOfContents] + endLevel = 3 + startLevel = 1 +[markup.goldmark.renderer] + unsafe = true \ No newline at end of file diff --git a/content/.DS_Store b/content/.DS_Store new file mode 100644 index 00000000..5008ddfc Binary files /dev/null and b/content/.DS_Store differ diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 00000000..8b6d728f --- /dev/null +++ b/content/_index.md @@ -0,0 +1,76 @@ +--- +title: "Index" +weight: 1 +# bookFlatSection: false +# bookToc: true +# bookHidden: false +# bookCollapseSection: false +# bookComments: false +# bookSearchExclude: false +--- + +![logo](logo.png) +{{< hint info >}} +**SCALE** (**S**imple **C**oncatenated **A**ggregate **L**ittle-**E**ndian) is the data format for types used in the Parity Substrate framework. It is a light-weight format which allows encoding (and decoding) which makes it highly suitable for resource-constrained execution environments like blockchain runtimes and low-power, low-memory devices. +{{< /hint >}} + +Welcome to the technical documentation of the [Rust implementation](https://github.com/paritytech/parity-scale-codec) of Parity's SCALE codec. This page is intended to serve as an introduction to SCALE for those new to Substrate development. For more detailed, low-level information about the `parity-scale-codec` Rust crate, please visit the corresponding [docs.rs page](https://docs.rs/parity-scale-codec/latest/parity_scale_codec/). + +This page is divided into the following sections: +- **Encode**: This section provides a practical introduction to how SCALE is used to encode types in Rust, complete with examples. It is recommended to read this section before proceeding to the Decode section. +- **Decode**: This section explains how to decode SCALE-encoded data and addresses common misconceptions and challenges related to SCALE's non-descriptive nature. +- **Use in Substrate**: This section outlines how SCALE is utilized in Substrate development and showcases common patterns. +- **Specification**: This section offers a brief overview of the SCALE encoding process. +- **SCALE crates**: This page provides a high-level overview of the various available SCALE Rust crates and their uses. + +SCALE is non-descriptive. This means that the encoding context, which includes knowledge of the types and data structures, must be known separately at both the encoding and decoding ends. The encoded data does not include this contextual information. Consider the following comparison between SCALE and JSON to understand what this means in practice. +{{< tabs "SCALEvsJSON" >}} +{{< tab "SCALE" >}} +```rust +use parity_scale_codec::{ Encode }; + +#[derive(Encode)] +struct Example { + number: u8, + is_cool: bool, + optional: Option, +} + +fn main() { + let my_struct = Example { + number: 42, + is_cool: true, + optional: Some(69), + }; + println!("{:?}", my_struct.encode()); + println!("{:?}", my_struct.encode().len()); +} +[42, 1, 1, 69, 0, 0, 0] +7 +``` +{{< /tab >}} +{{< tab "JSON" >}} +```rust +use serde::{ Serialize }; + +#[derive(Serialize)] +struct Example { + number: u8, + is_cool: bool, + optional: Option, +} + +fn main() { + let my_struct = Example { + number: 42, + is_cool: true, + optional: Some(69), + }; + println!("{:?}", serde_json::to_string(&my_struct).unwrap()); + println!("{:?}", serde_json::to_string(&my_struct).unwrap().len()); +} +"{\"number\":42,\"is_cool\":true,\"optional\":69}" +42 +``` +{{< /tab >}} +{{< /tabs >}} \ No newline at end of file diff --git a/content/_index/logo.png b/content/_index/logo.png new file mode 100755 index 00000000..15c8a22e Binary files /dev/null and b/content/_index/logo.png differ diff --git a/content/decode.md b/content/decode.md new file mode 100644 index 00000000..848778f8 --- /dev/null +++ b/content/decode.md @@ -0,0 +1,85 @@ +--- +title: "Decode" +weight: 3 +# bookFlatSection: false +# bookToc: true +# bookHidden: false +# bookCollapseSection: false +# bookComments: false +# bookSearchExclude: false +math: true +--- + + +# 1. Decoding +Since SCALE is non-descriptive, the proper metadata is needed to decode raw bytes into the appropriate types. + +```rust +use parity_scale_codec::{ Encode, Decode, DecodeAll }; + +fn main() { + let array = [0u8, 1u8, 2u8, 3u8]; + let value: u32 = 50462976; + + println!("{:02x?}", array.encode()); + println!("{:02x?}", value.encode()); + println!("{:?}", u32::decode(&mut &array.encode()[..])); + println!("{:?}", u16::decode(&mut &array.encode()[..])); + println!("{:?}", u16::decode_all(&mut &array.encode()[..])); + println!("{:?}", u64::decode(&mut &array.encode()[..])); +} +[00, 01, 02, 03] +[00, 01, 02, 03] +Ok(50462976) +Ok(256) +Err(Error { cause: None, desc: "Input buffer has still data left after decoding!" }) +Err(Error { cause: None, desc: "Not enough data to fill buffer" }) +``` +## 1.1 Depth Limit +Greater complexity in the decode type leads to increased computational resources used for value decoding. Generally you always want to `decode_with_depth_limit`. Substrate uses a limit of `256`. + +```rust +use parity_scale_codec_derive::{Encode, Decode}; +use parity_scale_codec::{Encode, Decode, DecodeLimit}; + +#[derive(Encode, Decode, Debug)] +enum Example { + First, + Second(Box), +} + +fn main() { + let bytes = vec![1, 1, 1, 1, 1, 0]; + println!("{:?}", Example::decode(&mut &bytes[..])); + println!("{:?}", Example::decode_with_depth_limit(10, &mut &bytes[..])); + println!("{:?}", Example::decode_with_depth_limit(3, &mut &bytes[..])); +} +Ok(Second(Second(Second(Second(Second(First)))))) +Ok(Second(Second(Second(Second(Second(First)))))) +Err(Error { cause: Some(Error { cause: Some(Error { cause: Some(Error { cause: Some(Error { cause: None, desc: "Maximum recursion depth reached when decoding" }), desc: "Could not decode `Example::Second.0`" }), desc: "Could not decode `Example::Second.0`" }), desc: "Could not decode `Example::Second.0`" }), desc: "Could not decode `Example::Second.0`" }) +``` + +## 1.2 When One-to-One Decoding Fails: `BTreeSet` + +SCALE is intended to be a one-to-one encoding, meaning the decoding process should return the exact data that was initially encoded. However, a notable exception occurs when using a `BTreeSet`. + +In Rust, a `BTreeSet` is a set data structure implemented using a B-tree, which keeps its elements sorted. This ordering is part of the internal functionality of the `BTreeSet` and doesn't usually concern users directly. However, this characteristic comes into play when encoding and then decoding data with SCALE. Consider the following example: +```rust +use parity_scale_codec::{ Encode, Decode, alloc::collections::BTreeSet }; + +fn main() { + let vector = vec![4u8, 3u8, 2u8, 1u8, 0u8]; + let vector_encoded = vector.encode(); + let btree = BTreeSet::::decode(&mut &vector_encoded[..]).unwrap(); + let btree_encoded = btree.encode(); + + println!("{:02x?}", vector_encoded); + println!("{:02x?}", btree_encoded); +} +[14, 04, 03, 02, 01, 00] +[14, 00, 01, 02, 03, 04] +``` +In this code, a vector of numbers is encoded, and then decoded into a `BTreeSet`. When the resulting `BTreeSet` is encoded again, the resulting data differs from the original encoded vector. This happens because the `BTreeSet` automatically sorts its elements upon decoding, resulting in a different ordering. + +It is essential to be aware of this behavior when using `BTreeSets` and similar datatypes in your Substrate code. Remember, SCALE encoding/decoding aims to be one-to-one, but the automated sorting feature of the `BTreeSet` breaks this expectation. This is not a failure of SCALE but a feature of the `BTreeSet` type itself. + diff --git a/content/encode.md b/content/encode.md new file mode 100644 index 00000000..5f151108 --- /dev/null +++ b/content/encode.md @@ -0,0 +1,247 @@ +--- +title: "Encode" +weight: 2 +# bookFlatSection: false +# bookToc: true +# bookHidden: false +# bookCollapseSection: false +# bookComments: false +# bookSearchExclude: false +math: true +--- + +# 1 Little-endian Encoding + +SCALE encoded types are stored in little-endian (LE) mode. Little-endian systems store the least significant byte at the smallest memory address. In contrast, big-endian (BE) systems store the most significant byte at the smallest memory address. For example, consider the `u32` integer given by the hexadecimal representation $\text{0x0a0b0c0d}$. In LE systems the least significant byte, that is $\text{0x0d}$, would be stored at the smallest memory address. The next diagram illustrates how the integer would be stored in memory for the two different modes. + +| Memory Address | $a$ | $a+1$ | $a+2$ | $a+3$ | ... | +|----------------|---|-----|-----|-----|-----| +|LE mode | $\text{0x0d}$ | $\text{0x0c}$ | $\text{0x0b}$ | $\text{0x0a}$ | +|BE mode | $\text{0x0a}$ | $\text{0x0b}$ | $\text{0x0c}$ | $\text{0x0d}$ | + +It's important to understand that endianness isn't a property of numbers themselves, and therefore, it isn't reflected in their binary or hexadecimal representations. However, Rust provides methods to handle endianness explicitly. For instance, the `to_le_bytes()` method can be used to obtain a little-endian byte vector representation of an integer. See the example below: + +```rust +fn main() { + println!("{:b}", 42u16); + println!("{:02x?}", 42u16.to_le_bytes()); + println!("{:b}", 16777215u32); + println!("{:02x?}", 16777215u32.to_le_bytes()); +} + +101010 +[2a, 00] +111111111111111111111111 +[ff, ff, ff, 00] +``` +In analogy to how the data would be stored in memory, the least significant byte is stored at the smallest vector index. Of course, this is only useful once the type is bigger than one byte. + +# 2 SCALE Encoding Basics +## 2.1 Introduction +This section offers practical examples to help you understand how to encode your types using SCALE. SCALE encoding is facilitated by the `Encode` trait offered by the `parity-scale-codec` crate. For an overview of how common types are encoded by SCALE, please refer to the [specification]({{< ref "/specification" >}}). + +To obtain the SCALE-encoded bytes as a `Vec`, use the `encode()` method on types that implement the `Encode` trait. For composite types such as structs and enums, you must first derive the `Encode` trait using the `parity-scale-codec-derive` crate before attempting to encode. + +```rust +use parity_scale_codec::Encode; +use parity_scale_codec_derive::Encode; + +#[derive(Encode)] +struct Example { + number: u8, + is_cool: bool, + optional: Option, +} + +fn main() { + let my_struct = Example { + number: 0, + is_cool: true, + optional: Some(69), + }; + println!("{:02x?}", [0u8, 1u8, 2u8, 3u8, 4u8].encode()); + println!("{:02x?}", (0u8, true, Some(69u32)).encode()); + println!("{:02x?}", my_struct.encode()); +} +[00, 01, 02, 03, 04] +[00, 01, 01, 45, 00, 00, 00] +[00, 01, 01, 45, 00, 00, 00] +``` + +In this example, the output demonstrates the encoded form of a byte array, a tuple, and a struct, all in hexadecimal format. + +## 2.2 Compact Integer Encoding + +Unsigned integers between $0$ and $2^{536} - 1$ can on average be more efficiently encoded using SCALE's compact encoding. While the ordinary fixed-width integer encoding depends on the size of the given integer's type (e.g. `u8`, `u16`, `u32`, ...), the compact encoding only looks at the number itself and disregards the type information. For example, the compact encodings of the integers `60u8`, `60u16` and `60u32` are all the same: $\text{Enc}\_{\text{SC}}^{\text{Comp}}(60) = \text{[0xf0]}$. + +Compact encoding can be utilized by enclosing the number within the `Compact` struct, as illustrated in the example below. + +```rust +use parity_scale_codec::{Compact, Encode}; + +fn main() { + println!("{:02x?}", 0u8.encode()); + println!("{:02x?}", 0u16.encode()); + println!("{:02x?}", 0u32.encode()); + println!("{:02x?}", Compact(60u8).encode()); + println!("{:02x?}", Compact(60u16).encode()); + println!("{:02x?}", Compact(60u32).encode()); +} +[00] +[00, 00] +[00, 00, 00, 00] +[f0] +[f0] +[f0] +``` + +There are four different modes in compact encoding. Which mode is used is automatically determined dependent on the size of the given integer. A quick overview of the cases is covered in the [specification](/docs/specification). Each mode is specified using a bit flag appended as the least significant two bits of the compact encoding. + +| Mode | Single-byte | Two-byte | Four-byte | Big-integer | +| -- | -- | -- | -- | -- | +| Bit flag | 00 | 01 | 10 | 11 | + +The following section provides more in-depth examples of how the different modes operate. It's not strictly necessary to understand how the values are encoded to use compact encoding. Since the appropriate mode is deduced automatically during encoding, there's no difference in usage from the perspective of a Rust user. + +In what follows $\text{0x}$ indicates the hexadecimal representation of a number and $\text{0b}$ indicates its binary representation. + +### 2.4.1 Single-byte mode +In this mode a given non-negative integer $n$ is encoded as a single byte. This is possible for $0 \leq n \leq 63$. Here the six most significant bits are the encoding of the value. As an example, consider the case $n = 42 = 2^5 + 2^3 + 2^1$. The compact encoding of $n$ is obtained by appending ${\color{red}00}$ as its least significant bits: + +$$ 42 = 0b101010 \Longrightarrow 0b101010{\color{red}00} = 168$$ + +Therefore, the compact encoding of $n$ is given by the byte array $\text{Enc}\_{\text{SC}}^{\text{Comp}}(n) = \text{[0xa8]}$. Since there's only one byte in this mode the LE aspect cannot be seen. + +```rust +use parity_scale_codec::{Compact, Encode}; + +fn main() { + println!("{:02x?}", 42u8.encode()); + println!("{:02x?}", 42u32.encode()); + println!("{:02x?}", Compact(42u8).encode()); + println!("{:02x?}", Compact(42u32).encode()); +} +[2a] +[2a, 00, 00, 00] +[a8] +[a8] +``` + +### 2.4.2 Two-byte mode +In this mode two bytes are used for the encoding. The six most significant bits of the first byte and the following byte are the LE encoding of the value. It applies for $2^6 \leq n \leq 2^{14} -1$. Consider the case $n = 69 = 2^6 + 2^2 + 2^0$. The compact encoding of $n$ is obtained by appending ${\color{red}01}$ as its least significant bits: +$$ 69 = 0b1000101 \Longrightarrow 0b1000101{\color{red}01} = 277.$$ +Since the resulting integer exceeds one byte, the number is split up starting with the least-significant byte. The compact encoding $\text{Enc}\_{\text{SC}}^{\text{Comp}}(n)$ is given by the byte array: +$$ 0b00000001\\;00010101 = \text{[0x15, 0x01]}.$$ + +```rust +use parity_scale_codec::{Compact, Encode}; + +fn main() { + println!("{:02x?}", 69u8.encode()); + println!("{:02x?}", 69u32.encode()); + println!("{:02x?}", Compact(69u8).encode()); + println!("{:02x?}", Compact(69u32).encode()); +} +[45] +[45, 00, 00, 00] +[15, 01] +[15, 01] +``` + +### 2.4.3 Four-byte mode +This mode uses four bytes to encode the value, which happens when $2^{14} \leq n \leq 2^{30} - 1$. Consider the case $n = 2^{16} - 1 = 65535$. This is the maximum value for the type `u16`. Its compact encoding is obtained by appending ${\color{red}10}$ as its least significant bits: +$$ 65535 = 0b11111111\\;11111111 \Longrightarrow 0b11111111\\;11111111{\color{red}10} = 262142.$$ +Analogously to the previous example, the resulting integer exceeds two bytes and needs to be split up using little-endian mode. Additionally, we pad with leading zeros. The compact encoding $\text{Enc}\_{\text{SC}}^{\text{Comp}}(n)$ is given by the byte array: +$$ 0b00000000\\;00000011\\;11111111\\;11111110 = \text{[0xfe, 0xff, 0x03, 0x00]}.$$ +```rust +use parity_scale_codec::{Compact, Encode}; + +fn main() { + println!("{:02x?}", 65535u16.encode()); + println!("{:02x?}", 65535u32.encode()); + println!("{:02x?}", Compact(65535u16).encode()); + println!("{:02x?}", Compact(65535u32).encode()); +} +[ff, ff] +[ff, ff, 00, 00] +[fe, ff, 03, 00] +[fe, ff, 03, 00] +``` +### 2.4.4 Big-integer mode + +This mode is intended for non-negative integers between $2^{30}$ and $2^{536} - 1$. It differs from the other three modes in that it is a variable length encoding. As a first example, consider the case $n = 2^{30} = 1073741824$. This number's LE encoding is given by: +$$0b 01000000\\;00000000\\;00000000\\;00000000 = \text{[0x00, 0x00, 0x00, 0x40]}.$$ + +Now, in big-integer mode, the six most significant bits of the first byte are used to store the number of bytes $m$ used in the actual encoding of the number *minus four*. That is $m - 4$. Since the LE encoding of $n$ is exactly of length $m = 4$, the upper six bits of the first byte must all be equal to zero. In accordance with the other cases, the mode is indicated using the two least significant bits of the first byte. For big-integer mode we append ${\color{red}11}$ to obtain as the first byte: +$$ 0 = m - 4 = 0b000000 \Longrightarrow 0b000000{\color{red}11} = 3.$$ +In total, the compact encoding $\text{Enc}\_{\text{SC}}^{\text{Comp}}(n)$ is given by the byte array: +$$\text{[0x03, 0x00, 0x00, 0x00, 0x40]}.$$ + +Let's look at another example. The LE encoding of the number $n = 2^{32} = 4294967296$ is given by $\text{[0x00, 0x00, 0x00, 0x00, 0x01]}.$ This time we need five bytes to store it, i.e. $m = 5$. Again, we use six bits to encode this length *minus four* and after that append ${\color{red}11}$: +$$ 1 = m - 4 = 0b000001 \Longrightarrow 0b000001{\color{red}11} = 7. $$ +Altogether, the compact encoding $\text{Enc}\_{\text{SC}}^{\text{Comp}}(n)$ is given by the byte array: +$$\text{[0x07, 0x00, 0x00, 0x00, 0x00, 0x01]}.$$ + +{{< hint info >}} +**Note**: The rationale behind storing $m-4$, rather than $m$ directly, lies in maximizing the efficiency of the available six bits, given that these bits set the limit for the size of integers we can compact encode. The smallest integer in big-integer mode, $2^{30}$, has a LE encoding that consists of $4$ bytes. Encoding this as $0b000010{\color{red}11}$ would inefficiently utilize the available space. By choosing to encode $m - 4$ instead, the first six bits can accommodate a length of $63 + 4$. This approach allows for the encoding of integers up to $2^{(63+4)8} - 1 = 2^{536} - 1$. +{{< /hint >}} + + +```rust +use parity_scale_codec::{Compact, Encode}; + +fn main() { + println!("{:02x?}", 1073741824u32.encode()); + println!("{:02x?}", 4294967296u64.encode()); + println!("{:02x?}", Compact(1073741824u32).encode()); + println!("{:02x?}", Compact(4294967296u64).encode()); +} +[00, 00, 00, 40] +[00, 00, 00, 00, 01, 00, 00, 00] +[03, 00, 00, 00, 40] +[07, 00, 00, 00, 00, 01] +``` + +## 2.3 Embedding Compact Encodings +We can also embed compact integer encodings within other types to make them more efficient. + +### 2.3.1 Structs + +By using the `codec(compact)` attribute of the `derive` macro we can specify that selected fields within a `struct` type will be compactly encoded. For example, in the following snippet we marked the `compact_number` field of the `Example` struct to be compactly encoded. + +```rust +use parity_scale_codec_derive::Encode; +use parity_scale_codec::Encode; + +#[derive(Encode)] +struct Example { + number: u64, + #[codec(compact)] + compact_number: u64, +} + +fn main() { + let my_struct = Example { number: 42, compact_number: 1337 }; + println!("{:02x?}", my_struct.encode()); +} +[2a, 00, 00, 00, 00, 00, 00, 00, e5, 14] +``` + +### 2.3.2 Enums +We can proceed similarly with `enums`. In this snippet only the second `u64` of the `One` variant will be compactly encoded. + +```rust +use parity_scale_codec_derive::Encode; +use parity_scale_codec::Encode; + +#[derive(Encode)] +enum Choices { + One(u64, #[codec(compact)] u64), +} + +fn main() { + let my_choice = Choices::One(42, 1337); + println!("{:02x?}", my_choice.encode()); +} +[00, 2a, 00, 00, 00, 00, 00, 00, 00, e5, 14] +``` \ No newline at end of file diff --git a/content/scale-crates.md b/content/scale-crates.md new file mode 100644 index 00000000..2998071c --- /dev/null +++ b/content/scale-crates.md @@ -0,0 +1,75 @@ +--- +title: "SCALE crates" +weight: 6 +# bookFlatSection: false +bookToc: true +# bookHidden: false +# bookCollapseSection: false +# bookComments: false +# bookSearchExclude: false +math: true +--- + +# 1. `scale-info` + +{{}} +flowchart RL + SI[[scale-info]] + SIT1(Registry) + SIT2(TypeInfo) + SIT1-- struct -->SI + SIT2-- trait -->SI +{{}} + +The `scale-info` Rust crate provides the essential tooling to handle type metadata in a compact, efficient manner compatible with the SCALE encoding. Its primary features include the `Registry` struct and the `TypeInfo` trait. + +The `Registry` serves as a database that associates each type with its corresponding metadata, providing a convenient and efficient means to access necessary type data. + +The `TypeInfo` trait, on the other hand, enables users to generate type information for any Rust type that implements this trait, which in turn can be registered in the `Registry`. + +These features of `scale-info` provide the underpinning for flexible encoding and decoding, by allowing types to describe themselves in a way that can be exploited by encoding and decoding tools such as the `scale-decode` crate. + +{{< hint info >}} +Further reading: {{< fontawesome "rust" >}}[docs.rs](https://docs.rs/scale-info/latest/scale_info/) | {{< fontawesome "github" >}}[Github](https://github.com/paritytech/scale-info) | {{< fontawesome "box" >}}[Crates.io](https://crates.io/crates/scale-info) +{{< /hint >}} + +# 2. `scale-decode` + +{{}} +flowchart TD + SD[[scale-decode]] + SDT1(Visitor) + SDT1-- trait -->SD + SDT4("decode_with_visitor + (bytes, typeid, registry, visitor)")-- function -->SD + SDT4-->Decode("SCALE bytes decoded into custom data structure") +{{}} + +The `scale-decode` crate facilitates the decoding of SCALE-encoded bytes into custom data structures by using type information from a `scale-info` registry. By implementing the `Visitor` trait and utilizing the `decode_with_visitor` function, developers can map decoded values to their chosen types with enhanced flexibility. + +{{< hint info >}} +Further reading: {{< fontawesome "rust" >}}[docs.rs](https://docs.rs/scale-decode/latest/scale_decode/) | {{< fontawesome "github" >}}[Github](https://github.com/paritytech/scale-decode) | {{< fontawesome "box" >}}[Crates.io](https://crates.io/crates/scale-decode/) +{{< /hint >}} + +# 3. `scale-value` + +{{}} +flowchart TD + SD[[scale-value]] + SDT1(Value) + SDT1-- struct -->SD +{{}} + +This crate provides a `Value` type, which is a runtime representation that is compatible with type descriptions from `scale-info`. It somewhat analogous to a `serde_json::Value`, which is a runtime representation of JSON values, but with a focus on SCALE encoded values instead of JSON encoded values. Unlike JSON however, SCALE encoding is not self describing, and so we need additional type information to tell us how to encode and decode values. It is expected that this crate will commonly be used in conjunction with the `scale-info` and `frame-metadata` crates. + +{{< hint info >}} +Further reading: {{< fontawesome "rust" >}}[docs.rs](https://docs.rs/scale-value/latest/scale_value/) | {{< fontawesome "github" >}}[Github](https://github.com/paritytech/scale-value) | {{< fontawesome "box" >}}[Crates.io](https://crates.io/crates/scale-value) +{{< /hint >}} + +# 4. `frame-metadata` + +While not directly a part of SCALE, the `frame-metadata` crate utilizes a `Registry` from the `scale-info` crate. The `frame-metadata` crate provides a struct that encapsulates metadata about a Substrate runtime. A notable aspect of this struct is a type registry, which is a collection of all types utilized in the metadata of the runtime. In addition, the struct comprises comprehensive data on the runtime's pallets, extrinsics, Runtime API, outer enums, and even accommodates custom metadata. The collective use of these elements allows developers to effectively navigate and adapt to the intricacies of a specific Substrate-based blockchain runtime. + +{{< hint info >}} +Further reading: {{< fontawesome "rust" >}}[docs.rs](https://docs.rs/frame-metadata/latest/frame_metadata/) | {{< fontawesome "github" >}}[Github](https://github.com/paritytech/frame-metadata) | {{< fontawesome "box" >}}[Crates.io](https://crates.io/crates/frame-metadata) +{{< /hint >}} \ No newline at end of file diff --git a/content/specification.md b/content/specification.md new file mode 100644 index 00000000..c74c3f2a --- /dev/null +++ b/content/specification.md @@ -0,0 +1,40 @@ +--- +title: "Specification" +weight: 5 +# bookFlatSection: false +bookToc: false +# bookHidden: false +# bookCollapseSection: false +# bookComments: false +# bookSearchExclude: false +math: true +--- + +# Specification + +SCALE defines encodings for native Rust types, and constructs encodings for composite types, such as structs, by concatenating the encodings of their constituents – that is, the elementary types that form these respective complex types. Additionally, some variable-length types are encoded with their length prefixed. In this way, the encoding of any type can be simplified to the concatenation of encodings of less complex types. + +This table offers a concise overview of the SCALE codec with examples. For more detailed, hands-on explanations, please refer to the [encode section]({{< ref "/encode" >}}). + +| Data type | Encoding Description | SCALE decoded value | SCALE encoded value | +| -- | -- | -- | -- | +| Unit | Encoded as an empty byte array. | `()` | `[]` | +| Boolean | Encoded using the least significant bit of a single byte. | `true` | `[01]` | +| | | `false`| `[00]` | +| Integer | By default integers are encoded using a fixed-width little-endian format. | `69i8` | `[2a]` | +| | | `69u32`| `[45, 00, 00, 00]`| +| | Unsigned integers $n$ also have a compact encoding. There are four modes. | | | | +| | Single-byte mode: Upper six bits are the LE encoding of the value. For $0 \leq n \leq 2^6 - 1$. |`0u8` | `[00]` | +| | Two-byte mode: Upper six bits and the following byte is the LE encoding of the value. For $2^6 \leq n \leq 2^{14} - 1$. |`69u8` | `[15, 01]` | +| | Four-byte mode: Upper six bits and the following three bytes are the LE encoding of the value. For $2^{14} \leq n \leq 2^{30} - 1$. |`65535u32` | `[fe, ff, 03, 00]` | +| | Big-integer mode: The upper six bits are the number of bytes following, minus four. The value is contained, LE encoded, in the bytes following. The final (most significant) byte must be non-zero. For $2^{30} \leq n \leq 2^{536} - 1$. |`1073741824u64` | `[03, 00, 00, 00, 40]` | +| Vector | Encoded by concatening the encodings of its items and prefixing with the compactly encoded length of the vector. |`vec![1u8, 2u8, 4u8]` | `[0c, 01, 02, 04]` | +| String | Encoded as `Vec` with UTF-8 characters. | `"SCALE♡"` | `[20, 53, 43, 41, 4c, 45, e2, 99, a1]` | +| Tuple, Struct, Array | Encoded by concatenating the encodings of their respective elements consecutively. |`(1u8, true, "OK")` | `[01, 01, 08, 4f, 4b]` | +| | | `MyStruct{id: 1u8, is_val: true, msg: "OK"}`| `[01, 01, 08, 4f, 4b]` | +| | |`[64u16, 512u16]` | `[40, 00, 00, 02]` | +| Enum | Encoded by the `u8`-index of the respective variant, followed by the encoded value if it is present. | `Example::Second(8u16)` | `[01, 08, 00]`| +| Result | Encoded by prefixing the encoded inner value with `0x00` if the operation was successful, and `0x01` if the operation was unsuccessful. |`Ok::(42u32)` | `[00, 2a, 00, 00, 00]` | +| | |`Err::(())` | `[01]` | +| Option | Encoded by prefixing the inner encoded value of `Some` with `0x01` and encoding `None` as `0x00`. |`Some(69u8)` | `[01, 45]` | +| | | `None::` | `[00]` | \ No newline at end of file diff --git a/content/use-in-substrate.md b/content/use-in-substrate.md new file mode 100644 index 00000000..d3863800 --- /dev/null +++ b/content/use-in-substrate.md @@ -0,0 +1,98 @@ +--- +title: "Use in Substrate" +weight: 4 +# bookFlatSection: false +# bookToc: true +# bookHidden: false +# bookCollapseSection: false +# bookComments: false +# bookSearchExclude: false +math: true +--- + +# 1. Using SCALE in Substrate Development + +## 1.1 General Workflow + +Pallets interact with the SCALE codec when their data structures need to be serialized for storage or network transmission, or deserialized for processing. The usage of SCALE in pallet and runtime development is straightforward and usually handled by simply deriving `Encode` and `Decode` for your data types. + +## 1.2 Case Study: Balances Pallet + +We illustrate this approach using an example taken from the [balances pallet](https://docs.rs/pallet-balances/22.0.0/pallet_balances/). + +First, the `AccountData` struct is defined in `types.rs`, with `Encode`, `Decode` and some other traits derived. This allows it to be automatically encoded and decoded when stored in Substrate's storage or when being part of the event parameters. + +```rust +/// All balance information for an account. +#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct AccountData { + pub free: Balance, + pub reserved: Balance, + pub frozen: Balance, + pub flags: ExtraFlags, +} +``` + +Next, the `balances` pallet uses the `AccountData` struct to represent all balance information for a given account. This data is stored in the `Account` storage map, where each `AccountId` is mapped to its corresponding `AccountData`. + +```rust +#[pallet::storage] +pub type Account, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::AccountId, AccountData, ValueQuery>; +``` +The `Account` storage map is part of the pallet's storage and defined within the `#[pallet::storage]` macro of the `lib.rs` file. With the `Encode` and `Decode` traits derived for `AccountData`, any data written to or read from this storage map will be automatically encoded or decoded. + +## 1.3 Automatic Decoding in Action + +When the balances pallet needs to read an account's balance from storage, the decoding happens automatically. Here is the `balance` function from the Balances pallet: + +```rust +fn balance(who: &T::AccountId) -> Self::Balance { + Self::account(who).free +} +``` +This function retrieves the `AccountData` of the given account from the storage, then returns the `free` balance field of the struct. The function chain involved in fetching the data from storage, decoding it, and accessing the data fields is abstracted away by the Substrate framework, demonstrating the utility of SCALE and Substrate's storage APIs. + +By following this pattern - defining your data types, deriving the appropriate traits, and using Substrate's storage APIs - you can seamlessly work with serialized data in your pallet development, keeping the complexity of serialization and deserialization hidden away. + +# 2. Common Patterns + +The following section introduces some important patterns used in Substrate. For a comprehensive list of traits employed in SCALE please refer to the [SCALE rust docs](https://docs.rs/parity-scale-codec/latest/parity_scale_codec/). + +## 2.1 The `MaxEncodedLen` Trait +The `MaxEncodedLen` trait is an important part of the SCALE encoding system utilized in Substrate. It provides a method for defining the maximum length, in bytes, that a type will take up when it is SCALE-encoded. This is particularly useful for putting an upper limit on the size of encoded data, enabling checks against this maximum length to reject overly large data. + +```rust +pub trait MaxEncodedLen: Encode { + /// Upper bound, in bytes, of the maximum encoded size of this item. + fn max_encoded_len() -> usize; +} +``` +A concrete example of its usage can be seen in Substrate's [democracy pallet](https://paritytech.github.io/substrate/master/pallet_democracy/index.html), specifically in how it is implemented for the `Vote` struct: +```rust +/// A number of lock periods, plus a vote, one way or the other. +#[derive(Copy, Clone, Eq, PartialEq, Default, RuntimeDebug)] +pub struct Vote { + pub aye: bool, + pub conviction: Conviction, +} +``` +The `Vote` struct contains two fields: `aye` (a boolean indicating a positive or negative vote) and `conviction` (an enum indicating the conviction level of the vote with $7$ variants). Despite the presence of multiple enum variants and a boolean, the Democracy pallet implements the `MaxEncodedLen` trait for Vote to fit within $1$ byte: +```rust +impl MaxEncodedLen for Vote { + fn max_encoded_len() -> usize { + 1 + } +} +``` +The encoding scheme for the `Vote` struct involves a clever utilization of the single byte's capacity. Here's how the `Vote` struct's `Encode` trait is implemented: +```rust +impl Encode for Vote { + fn encode_to(&self, output: &mut T) { + output.push_byte(u8::from(self.conviction) | if self.aye { 0b1000_0000 } else { 0 }); + } +} +``` +In this custom `Encode` implementation, the `Conviction` enum, which is represented as a `u8`, is encoded into the least significant $3$ bits of the byte. The `aye` bool, denoting whether the vote is in favor or against, is encoded into the most significant bit of the byte. If the vote is in favor (`aye` is true), the bit is set to $1$ ($0b10000000$ in binary), and if the vote is against (`aye` is false), the bit is set to $0$. + +This way, both `aye` and `conviction` are packed together into a single byte, ensuring that the data structure remains as compact as possible. This example demonstrates how the `MaxEncodedLen` trait can be effectively used to control the size of encoded data in Substrate pallet development. diff --git a/fontawesome/box.svg b/fontawesome/box.svg new file mode 100644 index 00000000..639ecd89 --- /dev/null +++ b/fontawesome/box.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fontawesome/github.svg b/fontawesome/github.svg new file mode 100644 index 00000000..783ca454 --- /dev/null +++ b/fontawesome/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/fontawesome/rust.svg b/fontawesome/rust.svg new file mode 100644 index 00000000..5c2ce653 --- /dev/null +++ b/fontawesome/rust.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..2027c012 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/repo/path + +go 1.20 diff --git a/index.html b/index.html deleted file mode 100644 index b79a5de0..00000000 --- a/index.html +++ /dev/null @@ -1 +0,0 @@ -SCALE CODEC diff --git a/resources/_gen/assets/scss/book.scss_e129fe35b8d0a70789c8a08429469073.content b/resources/_gen/assets/scss/book.scss_e129fe35b8d0a70789c8a08429469073.content new file mode 100644 index 00000000..6aa898bc --- /dev/null +++ b/resources/_gen/assets/scss/book.scss_e129fe35b8d0a70789c8a08429469073.content @@ -0,0 +1 @@ +@charset "UTF-8";:root{--gray-100:#f8f9fa;--gray-200:#e9ecef;--gray-500:#adb5bd;--color-link:#0055bb;--color-visited-link:#8440f1;--body-background:white;--body-font-color:black;--icon-filter:none;--hint-color-info:#6bf;--hint-color-warning:#fd6;--hint-color-danger:#f66}/*!normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css*/html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}.flex{display:flex}.flex-auto{flex:auto}.flex-even{flex:1 1}.flex-wrap{flex-wrap:wrap}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.align-center{align-items:center}.mx-auto{margin:0 auto}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.hidden{display:none}input.toggle{height:0;width:0;overflow:hidden;opacity:0;position:absolute}.clearfix::after{content:"";display:table;clear:both}html{font-size:16px;scroll-behavior:smooth;touch-action:manipulation}body{min-width:20rem;color:var(--body-font-color);background:var(--body-background);letter-spacing:.33px;font-weight:400;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;box-sizing:border-box}body *{box-sizing:inherit}h1,h2,h3,h4,h5{font-weight:400}a{text-decoration:none;color:var(--color-link)}img{vertical-align:baseline}:focus{outline-style:auto;outline-color:currentColor;outline-color:-webkit-focus-ring-color}aside nav ul{padding:0;margin:0;list-style:none}aside nav ul li{margin:1em 0;position:relative}aside nav ul a{display:block}aside nav ul a:hover{opacity:.5}aside nav ul ul{padding-inline-start:1rem}ul.pagination{display:flex;justify-content:center;list-style-type:none;padding-inline-start:0}ul.pagination .page-item a{padding:1rem}.container{max-width:80rem;margin:0 auto}.book-icon{filter:var(--icon-filter)}.book-brand{margin-top:0;margin-bottom:1rem}.book-brand img{height:1.5em;width:1.5em;margin-inline-end:.5rem}.book-menu{flex:0 0 16rem;font-size:.875rem}.book-menu .book-menu-content{width:16rem;padding:1rem;background:var(--body-background);position:fixed;top:0;bottom:0;overflow-x:hidden;overflow-y:auto}.book-menu a,.book-menu label{color:inherit;cursor:pointer;word-wrap:break-word}.book-menu a.active{color:var(--color-link)}.book-menu input.toggle+label+ul{display:none}.book-menu input.toggle:checked+label+ul{display:block}.book-menu input.toggle+label::after{content:"▸"}.book-menu input.toggle:checked+label::after{content:"▾"}body[dir=rtl] .book-menu input.toggle+label::after{content:"◂"}body[dir=rtl] .book-menu input.toggle:checked+label::after{content:"▾"}.book-section-flat{margin:2rem 0}.book-section-flat>a,.book-section-flat>span,.book-section-flat>label{font-weight:bolder}.book-section-flat>ul{padding-inline-start:0}.book-page{min-width:20rem;flex-grow:1;padding:1rem}.book-post{margin-bottom:3rem}.book-header{display:none;margin-bottom:1rem}.book-header label{line-height:0}.book-header img.book-icon{height:1.5em;width:1.5em}.book-search{position:relative;margin:1rem 0;border-bottom:1px solid transparent}.book-search input{width:100%;padding:.5rem;border:0;border-radius:.25rem;background:var(--gray-100);color:var(--body-font-color)}.book-search input:required+.book-search-spinner{display:block}.book-search .book-search-spinner{position:absolute;top:0;margin:.5rem;margin-inline-start:calc(100% - 1.5rem);width:1rem;height:1rem;border:1px solid transparent;border-top-color:var(--body-font-color);border-radius:50%;animation:spin 1s ease infinite}@keyframes spin{100%{transform:rotate(360deg)}}.book-search small{opacity:.5}.book-toc{flex:0 0 16rem;font-size:.75rem}.book-toc .book-toc-content{width:16rem;padding:1rem;position:fixed;top:0;bottom:0;overflow-x:hidden;overflow-y:auto}.book-toc img{height:1em;width:1em}.book-toc nav>ul>li:first-child{margin-top:0}.book-footer{padding-top:1rem;font-size:.875rem}.book-footer img{height:1em;width:1em;margin-inline-end:.5rem}.book-comments{margin-top:1rem}.book-languages{margin-block-end:2rem}.book-languages .book-icon{height:1em;width:1em;margin-inline-end:.5em}.book-languages ul{padding-inline-start:1.5em}.book-menu-content,.book-toc-content,.book-page,.book-header aside,.markdown{transition:.2s ease-in-out;transition-property:transform,margin,opacity,visibility;will-change:transform,margin,opacity}@media screen and (max-width:56rem){#menu-control,#toc-control{display:inline}.book-menu{visibility:hidden;margin-inline-start:-16rem;font-size:16px;z-index:1}.book-toc{display:none}.book-header{display:block}#menu-control:focus~main label[for=menu-control]{outline-style:auto;outline-color:currentColor;outline-color:-webkit-focus-ring-color}#menu-control:checked~main .book-menu{visibility:initial}#menu-control:checked~main .book-menu .book-menu-content{transform:translateX(16rem);box-shadow:0 0 .5rem rgba(0,0,0,.1)}#menu-control:checked~main .book-page{opacity:.25}#menu-control:checked~main .book-menu-overlay{display:block;position:absolute;top:0;bottom:0;left:0;right:0}#toc-control:focus~main label[for=toc-control]{outline-style:auto;outline-color:currentColor;outline-color:-webkit-focus-ring-color}#toc-control:checked~main .book-header aside{display:block}body[dir=rtl] #menu-control:checked~main .book-menu .book-menu-content{transform:translateX(-16rem)}}@media screen and (min-width:80rem){.book-page,.book-menu .book-menu-content,.book-toc .book-toc-content{padding:2rem 1rem}}@font-face{font-family:roboto;font-style:normal;font-weight:400;font-display:swap;src:local(""),url(fonts/roboto-v27-latin-regular.woff2)format("woff2"),url(fonts/roboto-v27-latin-regular.woff)format("woff")}@font-face{font-family:roboto;font-style:normal;font-weight:700;font-display:swap;src:local(""),url(fonts/roboto-v27-latin-700.woff2)format("woff2"),url(fonts/roboto-v27-latin-700.woff)format("woff")}@font-face{font-family:roboto mono;font-style:normal;font-weight:400;font-display:swap;src:local(""),url(fonts/roboto-mono-v13-latin-regular.woff2)format("woff2"),url(fonts/roboto-mono-v13-latin-regular.woff)format("woff")}body{font-family:roboto,sans-serif}code{font-family:roboto mono,monospace}@media print{.book-menu,.book-footer,.book-toc{display:none}.book-header,.book-header aside{display:block}main{display:block!important}}.markdown{line-height:1.6}.markdown>:first-child{margin-top:0}.markdown h1,.markdown h2,.markdown h3,.markdown h4,.markdown h5,.markdown h6{font-weight:400;line-height:1;margin-top:1.5em;margin-bottom:1rem}.markdown h1 a.anchor,.markdown h2 a.anchor,.markdown h3 a.anchor,.markdown h4 a.anchor,.markdown h5 a.anchor,.markdown h6 a.anchor{opacity:0;font-size:.75em;vertical-align:middle;text-decoration:none}.markdown h1:hover a.anchor,.markdown h1 a.anchor:focus,.markdown h2:hover a.anchor,.markdown h2 a.anchor:focus,.markdown h3:hover a.anchor,.markdown h3 a.anchor:focus,.markdown h4:hover a.anchor,.markdown h4 a.anchor:focus,.markdown h5:hover a.anchor,.markdown h5 a.anchor:focus,.markdown h6:hover a.anchor,.markdown h6 a.anchor:focus{opacity:initial}.markdown h4,.markdown h5,.markdown h6{font-weight:bolder}.markdown h5{font-size:.875em}.markdown h6{font-size:.75em}.markdown b,.markdown optgroup,.markdown strong{font-weight:bolder}.markdown a{text-decoration:none}.markdown a:hover{text-decoration:underline}.markdown a:visited{color:var(--color-visited-link)}.markdown img{max-width:100%;height:auto}.markdown code{padding:0 .25rem;background:var(--gray-200);border-radius:.25rem;font-size:.875em}.markdown pre{padding:1rem;background:var(--gray-100);border-radius:.25rem;overflow-x:auto}.markdown pre code{padding:0;background:0 0}.markdown p{word-wrap:break-word}.markdown blockquote{margin:1rem 0;padding:.5rem 1rem .5rem .75rem;border-inline-start:.25rem solid var(--gray-200);border-radius:.25rem}.markdown blockquote :first-child{margin-top:0}.markdown blockquote :last-child{margin-bottom:0}.markdown table{overflow:auto;display:block;border-spacing:0;border-collapse:collapse;margin-top:1rem;margin-bottom:1rem}.markdown table tr th,.markdown table tr td{padding:.5rem 1rem;border:1px solid var(--gray-200)}.markdown table tr:nth-child(2n){background:var(--gray-100)}.markdown hr{height:1px;border:none;background:var(--gray-200)}.markdown ul,.markdown ol{padding-inline-start:2rem;word-wrap:break-word}.markdown dl dt{font-weight:bolder;margin-top:1rem}.markdown dl dd{margin-inline-start:0;margin-bottom:1rem}.markdown .highlight table tr td:nth-child(1) pre{margin:0;padding-inline-end:0}.markdown .highlight table tr td:nth-child(2) pre{margin:0;padding-inline-start:0}.markdown details{padding:1rem;border:1px solid var(--gray-200);border-radius:.25rem}.markdown details summary{line-height:1;padding:1rem;margin:-1rem;cursor:pointer}.markdown details[open] summary{margin-bottom:0}.markdown figure{margin:1rem 0}.markdown figure figcaption p{margin-top:0}.markdown-inner>:first-child{margin-top:0}.markdown-inner>:last-child{margin-bottom:0}.markdown .book-expand{margin-top:1rem;margin-bottom:1rem;border:1px solid var(--gray-200);border-radius:.25rem;overflow:hidden}.markdown .book-expand .book-expand-head{background:var(--gray-100);padding:.5rem 1rem;cursor:pointer}.markdown .book-expand .book-expand-content{display:none;padding:1rem}.markdown .book-expand input[type=checkbox]:checked+.book-expand-content{display:block}.markdown .book-tabs{margin-top:1rem;margin-bottom:1rem;border:1px solid var(--gray-200);border-radius:.25rem;overflow:hidden;display:flex;flex-wrap:wrap}.markdown .book-tabs label{display:inline-block;padding:.5rem 1rem;border-bottom:1px transparent;cursor:pointer}.markdown .book-tabs .book-tabs-content{order:999;width:100%;border-top:1px solid var(--gray-100);padding:1rem;display:none}.markdown .book-tabs input[type=radio]:checked+label{border-bottom:1px solid var(--color-link)}.markdown .book-tabs input[type=radio]:checked+label+.book-tabs-content{display:block}.markdown .book-tabs input[type=radio]:focus+label{outline-style:auto;outline-color:currentColor;outline-color:-webkit-focus-ring-color}.markdown .book-columns{margin-left:-1rem;margin-right:-1rem}.markdown .book-columns>div{margin:1rem 0;min-width:10rem;padding:0 1rem}.markdown a.book-btn{display:inline-block;font-size:.875rem;color:var(--color-link);line-height:2rem;padding:0 1rem;border:1px solid var(--color-link);border-radius:.25rem;cursor:pointer}.markdown a.book-btn:hover{text-decoration:none}.markdown .book-hint.info{border-color:#6bf;background-color:rgba(102,187,255,.1)}.markdown .book-hint.warning{border-color:#fd6;background-color:rgba(255,221,102,.1)}.markdown .book-hint.danger{border-color:#f66;background-color:rgba(255,102,102,.1)} \ No newline at end of file diff --git a/resources/_gen/assets/scss/book.scss_e129fe35b8d0a70789c8a08429469073.json b/resources/_gen/assets/scss/book.scss_e129fe35b8d0a70789c8a08429469073.json new file mode 100644 index 00000000..eb1453a3 --- /dev/null +++ b/resources/_gen/assets/scss/book.scss_e129fe35b8d0a70789c8a08429469073.json @@ -0,0 +1 @@ +{"Target":"book.min.33a48f5432973b8ff9a82679d9e45d67f2c15d4399bd2829269455cfe390b5e8.css","MediaType":"text/css","Data":{"Integrity":"sha256-M6SPVDKXO4/5qCZ52eRdZ/LBXUOZvSgpJpRVz+OQteg="}} \ No newline at end of file diff --git a/static/logo.png b/static/logo.png new file mode 100755 index 00000000..15c8a22e Binary files /dev/null and b/static/logo.png differ diff --git a/static/parity-logo-square.png b/static/parity-logo-square.png new file mode 100644 index 00000000..46317fc7 Binary files /dev/null and b/static/parity-logo-square.png differ diff --git a/themes/.DS_Store b/themes/.DS_Store new file mode 100644 index 00000000..3dfc4dd2 Binary files /dev/null and b/themes/.DS_Store differ diff --git a/themes/hugo-book-master/.github/workflows/main.yml b/themes/hugo-book-master/.github/workflows/main.yml new file mode 100644 index 00000000..9ae4f54a --- /dev/null +++ b/themes/hugo-book-master/.github/workflows/main.yml @@ -0,0 +1,24 @@ +name: Build with Hugo + +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + hugo-version: + - 'latest' + - '0.79.0' + steps: + - uses: actions/checkout@v2 + + - name: Setup Hugo + uses: peaceiris/actions-hugo@v2 + with: + hugo-version: ${{ matrix.hugo-version }} + extended: true + + - name: Run Hugo + working-directory: exampleSite + run: hugo --themesDir ../.. diff --git a/themes/hugo-book-master/.gitignore b/themes/hugo-book-master/.gitignore new file mode 100644 index 00000000..5944200e --- /dev/null +++ b/themes/hugo-book-master/.gitignore @@ -0,0 +1,4 @@ +public/ +exampleSite/public/ +.DS_Store +.hugo_build.lock diff --git a/themes/hugo-book-master/LICENSE b/themes/hugo-book-master/LICENSE new file mode 100644 index 00000000..e7a669ab --- /dev/null +++ b/themes/hugo-book-master/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 Alex Shpak + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/themes/hugo-book-master/README.md b/themes/hugo-book-master/README.md new file mode 100644 index 00000000..be8d7648 --- /dev/null +++ b/themes/hugo-book-master/README.md @@ -0,0 +1,358 @@ +# Hugo Book Theme + +[![Hugo](https://img.shields.io/badge/hugo-0.79-blue.svg)](https://gohugo.io) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +![Build with Hugo](https://github.com/alex-shpak/hugo-book/workflows/Build%20with%20Hugo/badge.svg) + +### [Hugo](https://gohugo.io) documentation theme as simple as plain book + +![Screenshot](https://github.com/alex-shpak/hugo-book/blob/master/images/screenshot.png) + +- [Features](#features) +- [Requirements](#requirements) +- [Installation](#installation) +- [Menu](#menu) +- [Blog](#blog) +- [Configuration](#configuration) +- [Shortcodes](#shortcodes) +- [Versioning](#versioning) +- [Contributing](#contributing) + +## Features + +- Clean simple design +- Light and Mobile-Friendly +- Multi-language support +- Customisable +- Zero initial configuration +- Handy shortcodes +- Comments support +- Simple blog and taxonomy +- Primary features work without JavaScript +- Dark Mode + +## Requirements + +- Hugo 0.79 or higher +- Hugo extended version, read more [here](https://gohugo.io/news/0.48-relnotes/) + +## Installation + +### Install as git submodule +Navigate to your hugo project root and run: + +``` +git submodule add https://github.com/alex-shpak/hugo-book themes/hugo-book +``` + +Then run hugo (or set `theme = "hugo-book"`/`theme: hugo-book` in configuration file) + +``` +hugo server --minify --theme hugo-book +``` + +### Install as hugo module + +You can also add this theme as a Hugo module instead of a git submodule. + +Start with initializing hugo modules, if not done yet: +``` +hugo mod init github.com/repo/path +``` + +Navigate to your hugo project root and add [module] section to your `config.toml`: + +```toml +[module] +[[module.imports]] +path = 'github.com/alex-shpak/hugo-book' +``` + +Then, to load/update the theme module and run hugo: + +```sh +hugo mod get -u +hugo server --minify +``` + +### Creating site from scratch + +Below is an example on how to create a new site from scratch: + +```sh +hugo new site mydocs; cd mydocs +git init +git submodule add https://github.com/alex-shpak/hugo-book themes/hugo-book +cp -R themes/hugo-book/exampleSite/content.en/* ./content +``` + +```sh +hugo server --minify --theme hugo-book +``` + +## Menu + +### File tree menu (default) + +By default, the theme will render pages from the `content/docs` section as a menu in a tree structure. +You can set `title` and `weight` in the front matter of pages to adjust the order and titles in the menu. + +### Leaf bundle menu (Deprecated, to be removed in June 2022) + +You can also use leaf bundle and the content of its `index.md` file as menu. +Given you have the following file structure: + +``` +├── content +│ ├── docs +│ │ ├── page-one.md +│ │ └── page-two.md +│ └── posts +│ ├── post-one.md +│ └── post-two.md +``` + +Create a file `content/menu/index.md` with the content: + +```md ++++ +headless = true ++++ + +- [Book Example]({{< relref "/docs/" >}}) + - [Page One]({{< relref "/docs/page-one" >}}) + - [Page Two]({{< relref "/docs/page-two" >}}) +- [Blog]({{< relref "/posts" >}}) +``` + +And Enable it by setting `BookMenuBundle: /menu` in Site configuration. + +- [Example menu](https://github.com/alex-shpak/hugo-book/blob/master/exampleSite/content.en/menu/index.md) +- [Example config file](https://github.com/alex-shpak/hugo-book/blob/master/exampleSite/config.yaml) +- [Leaf bundles](https://gohugo.io/content-management/page-bundles/) + +## Blog + +A simple blog is supported in the section `posts`. +A blog is not the primary usecase of this theme, so it has only minimal features. + +## Configuration + +### Site Configuration + +There are a few configuration options that you can add to your `config.toml` file. +You can also see the `yaml` example [here](https://github.com/alex-shpak/hugo-book/blob/master/exampleSite/config.yaml). + +```toml +# (Optional) Set Google Analytics if you use it to track your website. +# Always put it on the top of the configuration file, otherwise it won't work +googleAnalytics = "UA-XXXXXXXXX-X" + +# (Optional) If you provide a Disqus shortname, comments will be enabled on +# all pages. +disqusShortname = "my-site" + +# (Optional) Set this to true if you use capital letters in file names +disablePathToLower = true + +# (Optional) Set this to true to enable 'Last Modified by' date and git author +# information on 'doc' type pages. +enableGitInfo = true + +# (Optional) Theme is intended for documentation use, therefore it doesn't render taxonomy. +# You can remove related files with config below +disableKinds = ['taxonomy', 'taxonomyTerm'] + +[params] + # (Optional, default light) Sets color theme: light, dark or auto. + # Theme 'auto' switches between dark and light modes based on browser/os preferences + BookTheme = 'light' + + # (Optional, default true) Controls table of contents visibility on right side of pages. + # Start and end levels can be controlled with markup.tableOfContents setting. + # You can also specify this parameter per page in front matter. + BookToC = true + + # (Optional, default none) Set the path to a logo for the book. If the logo is + # /static/logo.png then the path would be 'logo.png' + BookLogo = 'logo.png' + + # (Optional, default none) Set leaf bundle to render as side menu + # When not specified file structure and weights will be used + # Deprecated, to be removed in June 2022 + BookMenuBundle = '/menu' + + # (Optional, default docs) Specify section of content to render as menu + # You can also set value to "*" to render all sections to menu + BookSection = 'docs' + + # Set source repository location. + # Used for 'Last Modified' and 'Edit this page' links. + BookRepo = 'https://github.com/alex-shpak/hugo-book' + + # Specifies commit portion of the link to the page's last modified commit hash for 'doc' page + # type. + # Required if 'BookRepo' param is set. + # Value used to construct a URL consisting of BookRepo/BookCommitPath/ + # Github uses 'commit', Bitbucket uses 'commits' + BookCommitPath = 'commit' + + # Enable 'Edit this page' links for 'doc' page type. + # Disabled by default. Uncomment to enable. Requires 'BookRepo' param. + # Path must point to the site directory. + BookEditPath = 'edit/master/exampleSite' + + # (Optional, default January 2, 2006) Configure the date format used on the pages + # - In git information + # - In blog posts + BookDateFormat = 'Jan 2, 2006' + + # (Optional, default true) Enables search function with flexsearch, + # Index is built on fly, therefore it might slowdown your website. + # Configuration for indexing can be adjusted in i18n folder per language. + BookSearch = true + + # (Optional, default true) Enables comments template on pages + # By default partials/docs/comments.html includes Disqus template + # See https://gohugo.io/content-management/comments/#configure-disqus + # Can be overwritten by same param in page frontmatter + BookComments = true + + # /!\ This is an experimental feature, might be removed or changed at any time + # (Optional, experimental, default false) Enables portable links and link checks in markdown pages. + # Portable links meant to work with text editors and let you write markdown without {{< relref >}} shortcode + # Theme will print warning if page referenced in markdown does not exists. + BookPortableLinks = true + + # /!\ This is an experimental feature, might be removed or changed at any time + # (Optional, experimental, default false) Enables service worker that caches visited pages and resources for offline use. + BookServiceWorker = true +``` + +### Multi-Language Support + +Theme supports Hugo's [multilingual mode](https://gohugo.io/content-management/multilingual/), just follow configuration guide there. You can also tweak search indexing configuration per language in `i18n` folder. + +### Page Configuration + +You can specify additional params in the front matter of individual pages: + +```toml +# Set type to 'docs' if you want to render page outside of configured section or if you render section other than 'docs' +type = 'docs' + +# Set page weight to re-arrange items in file-tree menu (if BookMenuBundle not set) +weight = 10 + +# (Optional) Set to 'true' to mark page as flat section in file-tree menu (if BookMenuBundle not set) +bookFlatSection = false + +# (Optional) Set to hide nested sections or pages at that level. Works only with file-tree menu mode +bookCollapseSection = true + +# (Optional) Set true to hide page or section from side menu (if BookMenuBundle not set) +bookHidden = false + +# (Optional) Set 'false' to hide ToC from page +bookToC = true + +# (Optional) If you have enabled BookComments for the site, you can disable it for specific pages. +bookComments = true + +# (Optional) Set to 'false' to exclude page from search index. +bookSearchExclude = true + +# (Optional) Set explicit href attribute for this page in a menu (if BookMenuBundle not set) +bookHref = '' +``` + +### Partials + +There are layout partials available for you to easily override components of the theme in `layouts/partials/`. + +In addition to this, there are several empty partials you can override to easily add/inject code. + +| Empty Partial | Placement | +| -------------------------------------------------- | ------------------------------------------- | +| `layouts/partials/docs/inject/head.html` | Before closing `` tag | +| `layouts/partials/docs/inject/body.html` | Before closing `` tag | +| `layouts/partials/docs/inject/footer.html` | After page footer content | +| `layouts/partials/docs/inject/menu-before.html` | At the beginning of `