mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-12 14:09:25 +00:00
Compare commits
18 Commits
ff-0.14
...
dab1bab736
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dab1bab736 | ||
|
|
d46ca1e8d0 | ||
|
|
1a766ab773 | ||
|
|
df2ae10d2f | ||
|
|
b92ac4a15b | ||
|
|
51bae4fedc | ||
|
|
ee8b353132 | ||
|
|
a2d558ee34 | ||
|
|
3273a4b725 | ||
|
|
df87abbae0 | ||
|
|
fdf2ec8e92 | ||
|
|
f92fe922a6 | ||
|
|
121a48b55c | ||
|
|
dff9a04a8c | ||
|
|
2d8f70036a | ||
|
|
dd95494d9c | ||
|
|
653b0e0bbc | ||
|
|
d78c92bc3e |
2
.github/nightly-version
vendored
2
.github/nightly-version
vendored
@@ -1 +1 @@
|
||||
nightly-2025-01-01
|
||||
nightly-2025-02-01
|
||||
|
||||
2
.github/workflows/daily-deny.yml
vendored
2
.github/workflows/daily-deny.yml
vendored
@@ -21,4 +21,4 @@ jobs:
|
||||
run: cargo install --locked cargo-deny
|
||||
|
||||
- name: Run cargo deny
|
||||
run: cargo deny -L error --all-features check
|
||||
run: cargo deny -L error --all-features check --hide-inclusion-graph
|
||||
|
||||
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
uses: ./.github/actions/build-dependencies
|
||||
|
||||
- name: Install nightly rust
|
||||
run: rustup toolchain install ${{ steps.nightly.outputs.version }} --profile minimal -t wasm32-unknown-unknown -c clippy
|
||||
run: rustup toolchain install ${{ steps.nightly.outputs.version }} --profile minimal -t wasmv1-none -c clippy
|
||||
|
||||
- name: Run Clippy
|
||||
run: cargo +${{ steps.nightly.outputs.version }} clippy --all-features --all-targets -- -D warnings -A clippy::items_after_test_module
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
run: cargo install --locked cargo-deny
|
||||
|
||||
- name: Run cargo deny
|
||||
run: cargo deny -L error --all-features check
|
||||
run: cargo deny -L error --all-features check --hide-inclusion-graph
|
||||
|
||||
fmt:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
2
.github/workflows/pages.yml
vendored
2
.github/workflows/pages.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
uses: ./.github/actions/build-dependencies
|
||||
- name: Buld Rust docs
|
||||
run: |
|
||||
rustup toolchain install ${{ steps.nightly.outputs.version }} --profile minimal -t wasm32-unknown-unknown -c rust-docs
|
||||
rustup toolchain install ${{ steps.nightly.outputs.version }} --profile minimal -t wasmv1-none -c rust-docs
|
||||
RUSTDOCFLAGS="--cfg docsrs" cargo +${{ steps.nightly.outputs.version }} doc --workspace --all-features
|
||||
mv target/doc docs/_site/rust
|
||||
|
||||
|
||||
3833
Cargo.lock
generated
3833
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
34
Cargo.toml
34
Cargo.toml
@@ -103,31 +103,17 @@ members = [
|
||||
"coordinator",
|
||||
|
||||
"substrate/primitives",
|
||||
|
||||
"substrate/coins/primitives",
|
||||
"substrate/coins/pallet",
|
||||
|
||||
"substrate/dex/pallet",
|
||||
|
||||
"substrate/validator-sets/primitives",
|
||||
"substrate/validator-sets/pallet",
|
||||
|
||||
"substrate/genesis-liquidity/primitives",
|
||||
"substrate/genesis-liquidity/pallet",
|
||||
|
||||
"substrate/emissions/primitives",
|
||||
"substrate/emissions/pallet",
|
||||
|
||||
"substrate/economic-security/pallet",
|
||||
|
||||
"substrate/in-instructions/primitives",
|
||||
"substrate/in-instructions/pallet",
|
||||
|
||||
"substrate/signals/primitives",
|
||||
"substrate/signals/pallet",
|
||||
|
||||
"substrate/abi",
|
||||
|
||||
"substrate/coins",
|
||||
"substrate/validator-sets",
|
||||
"substrate/signals",
|
||||
"substrate/dex",
|
||||
"substrate/genesis-liquidity",
|
||||
"substrate/economic-security",
|
||||
"substrate/emissions",
|
||||
"substrate/in-instructions",
|
||||
|
||||
"substrate/runtime",
|
||||
"substrate/node",
|
||||
|
||||
@@ -208,6 +194,7 @@ directories-next = { path = "patches/directories-next" }
|
||||
[workspace.lints.clippy]
|
||||
unwrap_or_default = "allow"
|
||||
map_unwrap_or = "allow"
|
||||
needless_continue = "allow"
|
||||
borrow_as_ptr = "deny"
|
||||
cast_lossless = "deny"
|
||||
cast_possible_truncation = "deny"
|
||||
@@ -238,7 +225,6 @@ manual_string_new = "deny"
|
||||
match_bool = "deny"
|
||||
match_same_arms = "deny"
|
||||
missing_fields_in_debug = "deny"
|
||||
needless_continue = "deny"
|
||||
needless_pass_by_value = "deny"
|
||||
ptr_cast_constness = "deny"
|
||||
range_minus_one = "deny"
|
||||
|
||||
@@ -15,7 +15,7 @@ pub fn serai_db_key(
|
||||
///
|
||||
/// Creates a unit struct and a default implementation for the `key`, `get`, and `set`. The macro
|
||||
/// uses a syntax similar to defining a function. Parameters are concatenated to produce a key,
|
||||
/// they must be `scale` encodable. The return type is used to auto encode and decode the database
|
||||
/// they must be `borsh` serializable. The return type is used to auto (de)serialize the database
|
||||
/// value bytes using `borsh`.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -54,11 +54,10 @@ macro_rules! create_db {
|
||||
)?;
|
||||
impl$(<$($generic_name: $generic_type),+>)? $field_name$(<$($generic_name),+>)? {
|
||||
pub(crate) fn key($($arg: $arg_type),*) -> Vec<u8> {
|
||||
use scale::Encode;
|
||||
$crate::serai_db_key(
|
||||
stringify!($db_name).as_bytes(),
|
||||
stringify!($field_name).as_bytes(),
|
||||
($($arg),*).encode()
|
||||
&borsh::to_vec(&($($arg),*)).unwrap(),
|
||||
)
|
||||
}
|
||||
pub(crate) fn set(
|
||||
|
||||
@@ -42,7 +42,7 @@ messages = { package = "serai-processor-messages", path = "../processor/messages
|
||||
message-queue = { package = "serai-message-queue", path = "../message-queue" }
|
||||
tributary-sdk = { path = "./tributary-sdk" }
|
||||
|
||||
serai-client = { path = "../substrate/client", default-features = false, features = ["serai", "borsh"] }
|
||||
serai-client = { path = "../substrate/client", default-features = false, features = ["serai"] }
|
||||
|
||||
log = { version = "0.4", default-features = false, features = ["std"] }
|
||||
env_logger = { version = "0.10", default-features = false, features = ["humantime"] }
|
||||
|
||||
@@ -23,7 +23,7 @@ schnorrkel = { version = "0.11", default-features = false, features = ["std"] }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std", "derive"] }
|
||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||
serai-client = { path = "../../substrate/client", default-features = false, features = ["serai", "borsh"] }
|
||||
serai-client = { path = "../../substrate/client", default-features = false, features = ["serai"] }
|
||||
|
||||
log = { version = "0.4", default-features = false, features = ["std"] }
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ borsh = { version = "1", default-features = false, features = ["std", "derive",
|
||||
|
||||
serai-db = { path = "../../common/db", version = "0.1" }
|
||||
|
||||
serai-client = { path = "../../substrate/client", default-features = false, features = ["serai", "borsh"] }
|
||||
serai-client = { path = "../../substrate/client", default-features = false, features = ["serai"] }
|
||||
serai-cosign = { path = "../cosign" }
|
||||
tributary-sdk = { path = "../tributary-sdk" }
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ schnorrkel = { version = "0.11", default-features = false, features = ["std"] }
|
||||
hex = { version = "0.4", default-features = false, features = ["std"] }
|
||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||
|
||||
serai-client = { path = "../../../substrate/client", default-features = false, features = ["serai", "borsh"] }
|
||||
serai-client = { path = "../../../substrate/client", default-features = false, features = ["serai"] }
|
||||
serai-cosign = { path = "../../cosign" }
|
||||
tributary-sdk = { path = "../../tributary-sdk" }
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ borsh = { version = "1", default-features = false, features = ["std", "derive",
|
||||
|
||||
dkg = { path = "../../crypto/dkg", default-features = false, features = ["std"] }
|
||||
|
||||
serai-client = { path = "../../substrate/client", version = "0.1", default-features = false, features = ["serai", "borsh"] }
|
||||
serai-client = { path = "../../substrate/client", version = "0.1", default-features = false, features = ["serai"] }
|
||||
|
||||
log = { version = "0.4", default-features = false, features = ["std"] }
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ pub(crate) fn merkle(hash_args: &[[u8; 32]]) -> [u8; 32] {
|
||||
let zero = [0; 32];
|
||||
let mut interim;
|
||||
while hashes.len() > 1 {
|
||||
interim = Vec::with_capacity((hashes.len() + 1) / 2);
|
||||
interim = Vec::with_capacity(hashes.len().div_ceil(2));
|
||||
|
||||
let mut i = 0;
|
||||
while i < hashes.len() {
|
||||
|
||||
@@ -29,7 +29,7 @@ ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, fea
|
||||
dkg = { path = "../../crypto/dkg", default-features = false, features = ["std"] }
|
||||
schnorr = { package = "schnorr-signatures", path = "../../crypto/schnorr", default-features = false, features = ["std"] }
|
||||
|
||||
serai-client = { path = "../../substrate/client", default-features = false, features = ["serai", "borsh"] }
|
||||
serai-client = { path = "../../substrate/client", default-features = false, features = ["serai"] }
|
||||
|
||||
serai-db = { path = "../../common/db" }
|
||||
serai-task = { path = "../../common/task", version = "0.1" }
|
||||
|
||||
@@ -61,7 +61,7 @@ impl Topic {
|
||||
attempt: attempt + 1,
|
||||
round: SigningProtocolRound::Preprocess,
|
||||
}),
|
||||
Topic::SlashReport { .. } => None,
|
||||
Topic::SlashReport => None,
|
||||
Topic::Sign { id, attempt, round: _ } => {
|
||||
Some(Topic::Sign { id, attempt: attempt + 1, round: SigningProtocolRound::Preprocess })
|
||||
}
|
||||
@@ -83,7 +83,7 @@ impl Topic {
|
||||
}
|
||||
SigningProtocolRound::Share => None,
|
||||
},
|
||||
Topic::SlashReport { .. } => None,
|
||||
Topic::SlashReport => None,
|
||||
Topic::Sign { id, attempt, round } => match round {
|
||||
SigningProtocolRound::Preprocess => {
|
||||
let attempt = attempt + 1;
|
||||
@@ -102,7 +102,7 @@ impl Topic {
|
||||
match self {
|
||||
Topic::RemoveParticipant { .. } => None,
|
||||
Topic::DkgConfirmation { .. } => None,
|
||||
Topic::SlashReport { .. } => None,
|
||||
Topic::SlashReport => None,
|
||||
Topic::Sign { id, attempt, round: _ } => Some(SignId { session: set.session, id, attempt }),
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,7 @@ impl Topic {
|
||||
};
|
||||
SignId { session: set.session, id, attempt }
|
||||
}),
|
||||
Topic::SlashReport { .. } => None,
|
||||
Topic::SlashReport => None,
|
||||
Topic::Sign { .. } => None,
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,7 @@ impl Topic {
|
||||
Some(Topic::DkgConfirmation { attempt, round: SigningProtocolRound::Preprocess })
|
||||
}
|
||||
},
|
||||
Topic::SlashReport { .. } => None,
|
||||
Topic::SlashReport => None,
|
||||
Topic::Sign { id, attempt, round } => match round {
|
||||
SigningProtocolRound::Preprocess => None,
|
||||
SigningProtocolRound::Share => {
|
||||
@@ -170,7 +170,7 @@ impl Topic {
|
||||
}
|
||||
SigningProtocolRound::Share => None,
|
||||
},
|
||||
Topic::SlashReport { .. } => None,
|
||||
Topic::SlashReport => None,
|
||||
Topic::Sign { id, attempt, round } => match round {
|
||||
SigningProtocolRound::Preprocess => {
|
||||
Some(Topic::Sign { id, attempt, round: SigningProtocolRound::Share })
|
||||
@@ -189,7 +189,7 @@ impl Topic {
|
||||
// We don't require recognition for the first attempt, solely the re-attempts
|
||||
Topic::DkgConfirmation { attempt, .. } => *attempt != 0,
|
||||
// We don't require recognition for the slash report
|
||||
Topic::SlashReport { .. } => false,
|
||||
Topic::SlashReport => false,
|
||||
// We do require recognition for every sign protocol
|
||||
Topic::Sign { .. } => true,
|
||||
}
|
||||
@@ -206,7 +206,7 @@ impl Topic {
|
||||
match self {
|
||||
Topic::RemoveParticipant { .. } => Participating::Everyone,
|
||||
Topic::DkgConfirmation { .. } => Participating::Participated,
|
||||
Topic::SlashReport { .. } => Participating::Everyone,
|
||||
Topic::SlashReport => Participating::Everyone,
|
||||
Topic::Sign { .. } => Participating::Participated,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,8 @@ fn weight<D: Send + Clone + SecureDigest, F: PrimeField>(digest: &mut DigestTran
|
||||
// Derive a scalar from enough bits of entropy that bias is < 2^128
|
||||
// This can't be const due to its usage of a generic
|
||||
// Also due to the usize::try_from, yet that could be replaced with an `as`
|
||||
// The + 7 forces it to round up
|
||||
#[allow(non_snake_case)]
|
||||
let BYTES: usize = usize::try_from(((F::NUM_BITS + 128) + 7) / 8).unwrap();
|
||||
let BYTES: usize = usize::try_from((F::NUM_BITS + 128).div_ceil(8)).unwrap();
|
||||
|
||||
let mut remaining = BYTES;
|
||||
|
||||
|
||||
@@ -7,8 +7,6 @@ db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||
yanked = "deny"
|
||||
|
||||
ignore = [
|
||||
"RUSTSEC-2020-0168", # mach is unmaintained
|
||||
"RUSTSEC-2021-0139", # https://github.com/serai-dex/serai/228
|
||||
"RUSTSEC-2022-0061", # https://github.com/serai-dex/serai/227
|
||||
"RUSTSEC-2024-0370", # proc-macro-error is unmaintained
|
||||
"RUSTSEC-2024-0384", # instant is unmaintained
|
||||
@@ -123,7 +121,7 @@ wildcards = "warn"
|
||||
highlight = "all"
|
||||
deny = [
|
||||
{ name = "serde_derive", version = ">=1.0.172, <1.0.185" },
|
||||
{ name = "hashbrown", version = ">=0.15" },
|
||||
{ name = "hashbrown", version = "=0.15.0" },
|
||||
]
|
||||
|
||||
[sources]
|
||||
@@ -133,6 +131,6 @@ allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
||||
allow-git = [
|
||||
"https://github.com/rust-lang-nursery/lazy-static.rs",
|
||||
"https://github.com/serai-dex/substrate-bip39",
|
||||
"https://github.com/serai-dex/substrate",
|
||||
"https://github.com/serai-dex/polkadot-sdk",
|
||||
"https://github.com/kayabaNerve/pasta_curves",
|
||||
]
|
||||
|
||||
@@ -46,7 +46,7 @@ serai-db = { path = "../common/db", optional = true }
|
||||
|
||||
serai-env = { path = "../common/env" }
|
||||
|
||||
serai-primitives = { path = "../substrate/primitives", features = ["borsh"] }
|
||||
serai-primitives = { path = "../substrate/primitives", default-features = false, features = ["std"] }
|
||||
|
||||
[features]
|
||||
parity-db = ["serai-db/parity-db"]
|
||||
|
||||
@@ -11,7 +11,7 @@ RUN rm -rf /etc/apt/sources.list.d/debian.sources && \
|
||||
RUN apt update && apt upgrade && apt install clang -y
|
||||
|
||||
# Add the wasm toolchain
|
||||
RUN rustup target add wasm32-unknown-unknown
|
||||
RUN rustup target add wasmv1-none
|
||||
|
||||
FROM deterministic
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ RUN apt install -y pkg-config clang
|
||||
RUN apt install -y make protobuf-compiler
|
||||
|
||||
# Add the wasm toolchain
|
||||
RUN rustup target add wasm32-unknown-unknown
|
||||
RUN rustup target add wasmv1-none
|
||||
|
||||
{prelude}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ rand_core = { version = "0.6", default-features = false, features = ["std", "get
|
||||
|
||||
frost = { package = "modular-frost", path = "../../crypto/frost", version = "^0.8.1", default-features = false }
|
||||
|
||||
serai-validator-sets-primitives = { path = "../../substrate/validator-sets/primitives", default-features = false, features = ["std"] }
|
||||
serai-primitives = { path = "../../substrate/primitives", default-features = false, features = ["std"] }
|
||||
|
||||
log = { version = "0.4", default-features = false, features = ["std"] }
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, fea
|
||||
dkg = { package = "dkg", path = "../../crypto/dkg", default-features = false, features = ["std", "evrf-ristretto"] }
|
||||
|
||||
# Substrate
|
||||
serai-validator-sets-primitives = { path = "../../substrate/validator-sets/primitives", default-features = false, features = ["std"] }
|
||||
serai-primitives = { path = "../../substrate/primitives", default-features = false, features = ["std"] }
|
||||
|
||||
# Encoders
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std"] }
|
||||
|
||||
@@ -25,9 +25,6 @@ borsh = { version = "1", default-features = false, features = ["std", "derive",
|
||||
|
||||
dkg = { path = "../../crypto/dkg", default-features = false, features = ["std", "borsh"] }
|
||||
|
||||
serai-primitives = { path = "../../substrate/primitives", default-features = false, features = ["std", "borsh"] }
|
||||
in-instructions-primitives = { package = "serai-in-instructions-primitives", path = "../../substrate/in-instructions/primitives", default-features = false, features = ["std", "borsh"] }
|
||||
coins-primitives = { package = "serai-coins-primitives", path = "../../substrate/coins/primitives", default-features = false, features = ["std", "borsh"] }
|
||||
validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../../substrate/validator-sets/primitives", default-features = false, features = ["std", "borsh"] }
|
||||
serai-primitives = { path = "../../substrate/primitives", default-features = false, features = ["std"] }
|
||||
|
||||
serai-cosign = { path = "../../coordinator/cosign", default-features = false }
|
||||
|
||||
@@ -20,8 +20,7 @@ workspace = true
|
||||
[dependencies]
|
||||
group = { version = "0.13", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../../substrate/primitives", default-features = false, features = ["std", "borsh"] }
|
||||
serai-coins-primitives = { path = "../../substrate/coins/primitives", default-features = false, features = ["std", "borsh"] }
|
||||
serai-primitives = { path = "../../substrate/primitives", default-features = false, features = ["std"] }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std"] }
|
||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||
|
||||
@@ -36,9 +36,6 @@ serai-db = { path = "../../common/db" }
|
||||
messages = { package = "serai-processor-messages", path = "../messages" }
|
||||
|
||||
serai-primitives = { path = "../../substrate/primitives", default-features = false, features = ["std"] }
|
||||
serai-validator-sets-primitives = { path = "../../substrate/validator-sets/primitives", default-features = false, features = ["std", "borsh"] }
|
||||
serai-in-instructions-primitives = { path = "../../substrate/in-instructions/primitives", default-features = false, features = ["std", "borsh"] }
|
||||
serai-coins-primitives = { path = "../../substrate/coins/primitives", default-features = false, features = ["std", "borsh"] }
|
||||
|
||||
primitives = { package = "serai-processor-primitives", path = "../primitives" }
|
||||
scheduler-primitives = { package = "serai-processor-scheduler-primitives", path = "../scheduler/primitives" }
|
||||
|
||||
@@ -33,8 +33,6 @@ scale = { package = "parity-scale-codec", version = "3", default-features = fals
|
||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||
|
||||
serai-primitives = { path = "../../substrate/primitives", default-features = false, features = ["std"] }
|
||||
serai-validator-sets-primitives = { path = "../../substrate/validator-sets/primitives", default-features = false, features = ["std"] }
|
||||
serai-in-instructions-primitives = { path = "../../substrate/in-instructions/primitives", default-features = false, features = ["std"] }
|
||||
|
||||
serai-db = { path = "../../common/db" }
|
||||
log = { version = "0.4", default-features = false, features = ["std"] }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[toolchain]
|
||||
channel = "1.81"
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
channel = "1.85"
|
||||
targets = ["wasmv1-none"]
|
||||
profile = "minimal"
|
||||
components = ["rust-src", "rustfmt", "clippy"]
|
||||
components = ["rustfmt", "clippy"]
|
||||
|
||||
@@ -27,9 +27,9 @@ brew install rustup
|
||||
```
|
||||
rustup update
|
||||
rustup toolchain install stable
|
||||
rustup target add wasm32-unknown-unknown
|
||||
rustup target add wasmv1-none
|
||||
rustup toolchain install nightly
|
||||
rustup target add wasm32-unknown-unknown --toolchain nightly
|
||||
rustup target add wasmv1-none --toolchain nightly
|
||||
```
|
||||
|
||||
### Install Solidity
|
||||
|
||||
@@ -12,76 +12,41 @@ rust-version = "1.80"
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["serde"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
bitvec = { version = "1", default-features = false, features = ["alloc", "serde"] }
|
||||
borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"] }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "bit-vec"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive", "bit-vec"] }
|
||||
bitvec = { version = "1", default-features = false, features = ["alloc"] }
|
||||
sp-core = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
|
||||
borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"], optional = true }
|
||||
serde = { version = "1", default-features = false, features = ["derive", "alloc"], optional = true }
|
||||
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
sp-consensus-babe = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-consensus-grandpa = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
serde = { version = "1", default-features = false, features = ["derive"], optional = true }
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"], optional = true }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"], optional = true }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false, features = ["serde"], optional = true }
|
||||
frame-support = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false, optional = true }
|
||||
|
||||
serai-primitives = { path = "../primitives", version = "0.1", default-features = false }
|
||||
serai-coins-primitives = { path = "../coins/primitives", version = "0.1", default-features = false }
|
||||
serai-validator-sets-primitives = { path = "../validator-sets/primitives", version = "0.1", default-features = false }
|
||||
serai-genesis-liquidity-primitives = { path = "../genesis-liquidity/primitives", version = "0.1", default-features = false }
|
||||
serai-emissions-primitives = { path = "../emissions/primitives", version = "0.1", default-features = false }
|
||||
serai-in-instructions-primitives = { path = "../in-instructions/primitives", version = "0.1", default-features = false }
|
||||
serai-signals-primitives = { path = "../signals/primitives", version = "0.1", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"borsh/std",
|
||||
|
||||
"bitvec/std",
|
||||
|
||||
"scale/std",
|
||||
"scale-info/std",
|
||||
|
||||
"borsh?/std",
|
||||
"serde?/std",
|
||||
|
||||
"sp-core/std",
|
||||
"sp-runtime/std",
|
||||
|
||||
"sp-consensus-babe/std",
|
||||
"sp-consensus-grandpa/std",
|
||||
|
||||
"frame-support/std",
|
||||
"serde?/std",
|
||||
"scale?/std",
|
||||
"scale-info?/std",
|
||||
"sp-runtime?/std",
|
||||
"frame-support?/std",
|
||||
|
||||
"serai-primitives/std",
|
||||
"serai-coins-primitives/std",
|
||||
"serai-validator-sets-primitives/std",
|
||||
"serai-genesis-liquidity-primitives/std",
|
||||
"serai-emissions-primitives/std",
|
||||
"serai-in-instructions-primitives/std",
|
||||
"serai-signals-primitives/std",
|
||||
]
|
||||
borsh = [
|
||||
"dep:borsh",
|
||||
"serai-primitives/borsh",
|
||||
"serai-coins-primitives/borsh",
|
||||
"serai-validator-sets-primitives/borsh",
|
||||
"serai-genesis-liquidity-primitives/borsh",
|
||||
"serai-in-instructions-primitives/borsh",
|
||||
"serai-signals-primitives/borsh",
|
||||
]
|
||||
serde = [
|
||||
"dep:serde",
|
||||
"serai-primitives/serde",
|
||||
"serai-coins-primitives/serde",
|
||||
"serai-validator-sets-primitives/serde",
|
||||
"serai-genesis-liquidity-primitives/serde",
|
||||
"serai-in-instructions-primitives/serde",
|
||||
"serai-signals-primitives/serde",
|
||||
]
|
||||
substrate = ["serde", "scale", "scale-info", "sp-runtime", "frame-support", "serai-primitives/non_canonical_scale_derivations"]
|
||||
try-runtime = ["sp-runtime/try-runtime"]
|
||||
default = ["std"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Luke Parker
|
||||
Copyright (c) 2023-2025 Luke Parker
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
4
substrate/abi/README.md
Normal file
4
substrate/abi/README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# serai-abi
|
||||
|
||||
Serai's ABI, inclusive to the transaction, event, and block types. MIT-licensed to ensure usability
|
||||
in a variety of contexts.
|
||||
@@ -1,17 +0,0 @@
|
||||
use sp_consensus_babe::EquivocationProof;
|
||||
|
||||
use serai_primitives::{Header, SeraiAddress};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
pub struct ReportEquivocation {
|
||||
pub equivocation_proof: alloc::boxed::Box<EquivocationProof<Header>>,
|
||||
pub key_owner_proof: SeraiAddress,
|
||||
}
|
||||
|
||||
// We could define a Babe Config here and use the literal pallet_babe::Call
|
||||
// The disadvantage to this would be the complexity and presence of junk fields such as `__Ignore`
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
pub enum Call {
|
||||
report_equivocation(ReportEquivocation),
|
||||
report_equivocation_unsigned(ReportEquivocation),
|
||||
}
|
||||
360
substrate/abi/src/block.rs
Normal file
360
substrate/abi/src/block.rs
Normal file
@@ -0,0 +1,360 @@
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
use crate::{
|
||||
primitives::{BlockHash, merkle::UnbalancedMerkleTree},
|
||||
Transaction,
|
||||
};
|
||||
|
||||
/// The tag for the hash of a transaction's event, forming a leaf of the Merkle tree of its events.
|
||||
pub const TRANSACTION_EVENTS_COMMITMENT_LEAF_TAG: u8 = 0;
|
||||
/// The tag for the branch hashes of transaction events.
|
||||
pub const TRANSACTION_EVENTS_COMMITMENT_BRANCH_TAG: u8 = 1;
|
||||
/// The tag for the hash of a transaction's hash and its events' Merkle root, forming a leaf of the
|
||||
/// Merkle tree which is the events commitment.
|
||||
pub const EVENTS_COMMITMENT_LEAF_TAG: u8 = 2;
|
||||
/// The tag for for the branch hashes of the Merkle tree which is the events commitments.
|
||||
pub const EVENTS_COMMITMENT_BRANCH_TAG: u8 = 3;
|
||||
|
||||
/// A V1 header for a block.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub struct HeaderV1 {
|
||||
/// The index of this block on the blockchain.
|
||||
///
|
||||
/// The genesis block has number 0.
|
||||
pub number: u64,
|
||||
/// The commitment to the DAG this header builds upon.
|
||||
///
|
||||
/// This is defined as an unbalanced Merkle tree so light clients may sync one header per epoch,
|
||||
/// and then may prove the inclusion of any header in logarithmic depth (without providing the
|
||||
/// entire header chain).
|
||||
///
|
||||
/// Alternative popular options would be a Merkle Mountain Range, which makes more recent blocks
|
||||
/// cheaper to prove at the sacrifice of older blocks being more expensive to prove. An MMR isn't
|
||||
/// used in order to minimize the protocol's surface area. Additionally, even though the
|
||||
/// unbalanced Merkle tree doesn't achieve such notably short paths for recent blocks, it does
|
||||
/// inherently provide lower-depth paths to more recent items *on imbalance*.
|
||||
pub builds_upon: UnbalancedMerkleTree,
|
||||
/// The UNIX time in milliseconds this block was created at.
|
||||
pub unix_time_in_millis: u64,
|
||||
/// The commitment to the transactions within this block.
|
||||
pub transactions_commitment: UnbalancedMerkleTree,
|
||||
/// The commitment to the events within this block.
|
||||
///
|
||||
/// The leaves of this tree will be of the form
|
||||
/// `(EVENTS_COMMITMENT_LEAF_TAG, transaction hash, transaction's events' Merkle tree root)`.
|
||||
/// A transaction may have the same event multiple times, yet an event may be uniquely identified
|
||||
/// by its path within the tree.
|
||||
pub events_commitment: UnbalancedMerkleTree,
|
||||
/// A commitment to the consensus data used to justify adding this block to the blockchain.
|
||||
pub consensus_commitment: [u8; 32],
|
||||
}
|
||||
|
||||
/// A header for a block.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Header {
|
||||
/// A version 1 header.
|
||||
V1(HeaderV1),
|
||||
}
|
||||
|
||||
impl Header {
|
||||
/// Get the hash of the header.
|
||||
pub fn number(&self) -> u64 {
|
||||
match self {
|
||||
Header::V1(HeaderV1 { number, .. }) => *number,
|
||||
}
|
||||
}
|
||||
/// Get the commitment to the DAG this header builds upon.
|
||||
pub fn builds_upon(&self) -> UnbalancedMerkleTree {
|
||||
match self {
|
||||
Header::V1(HeaderV1 { builds_upon, .. }) => *builds_upon,
|
||||
}
|
||||
}
|
||||
/// The commitment to the transactions within this block.
|
||||
pub fn transactions_commitment(&self) -> UnbalancedMerkleTree {
|
||||
match self {
|
||||
Header::V1(HeaderV1 { transactions_commitment, .. }) => *transactions_commitment,
|
||||
}
|
||||
}
|
||||
/// The commitment to the events within this block.
|
||||
pub fn events_commitment(&self) -> UnbalancedMerkleTree {
|
||||
match self {
|
||||
Header::V1(HeaderV1 { events_commitment, .. }) => *events_commitment,
|
||||
}
|
||||
}
|
||||
/// Get the hash of the header.
|
||||
pub fn hash(&self) -> BlockHash {
|
||||
BlockHash(sp_core::blake2_256(&borsh::to_vec(self).unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
/// A block.
|
||||
///
|
||||
/// This does not guarantee consistency. The header's `transactions_root` may not match the
|
||||
/// contained transactions.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub struct Block {
|
||||
/// The block's header.
|
||||
pub header: Header,
|
||||
/// The block's transactions.
|
||||
pub transactions: Vec<Transaction>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "substrate")]
|
||||
mod substrate {
|
||||
use core::fmt::Debug;
|
||||
|
||||
use scale::{Encode, Decode};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{
|
||||
generic::{DigestItem, Digest},
|
||||
traits::{Header as HeaderTrait, HeaderProvider, Block as BlockTrait},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// The digest for all of the Serai-specific header fields added before execution of the block.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||
pub struct SeraiPreExecutionDigest {
|
||||
/// The UNIX time in milliseconds this block was created at.
|
||||
pub unix_time_in_millis: u64,
|
||||
}
|
||||
|
||||
/// The digest for all of the Serai-specific header fields determined during execution of the
|
||||
/// block.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||
pub struct SeraiExecutionDigest {
|
||||
/// The commitment to the DAG this header builds upon.
|
||||
pub builds_upon: UnbalancedMerkleTree,
|
||||
/// The commitment to the transactions within this block.
|
||||
pub transactions_commitment: UnbalancedMerkleTree,
|
||||
/// The commitment to the events within this block.
|
||||
pub events_commitment: UnbalancedMerkleTree,
|
||||
}
|
||||
|
||||
impl SeraiPreExecutionDigest {
|
||||
/// The consensus ID for a Serai pre-execution digest.
|
||||
pub const CONSENSUS_ID: [u8; 4] = *b"SRIP";
|
||||
}
|
||||
|
||||
impl SeraiExecutionDigest {
|
||||
/// The consensus ID for a Serai execution digest.
|
||||
pub const CONSENSUS_ID: [u8; 4] = *b"SRIE";
|
||||
}
|
||||
|
||||
/// The consensus data for a V1 header.
|
||||
///
|
||||
/// This is not considered part of the protocol proper and may be pruned in the future. It's
|
||||
/// solely considered used for consensus now.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, sp_runtime::Serialize)]
|
||||
pub struct ConsensusV1 {
|
||||
/// The hash of the immediately preceding block.
|
||||
parent_hash: H256,
|
||||
/// The root for the Merkle tree of transactions, as defined by Substrate.
|
||||
///
|
||||
/// The format of this differs from Serai's format for the commitment to the transactions.
|
||||
transactions_root: H256,
|
||||
/// The state root.
|
||||
state_root: H256,
|
||||
/// The consensus digests.
|
||||
digest: Digest,
|
||||
}
|
||||
|
||||
/// A V1 header for a block, as needed by Substrate.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, sp_runtime::Serialize)]
|
||||
pub struct SubstrateHeaderV1 {
|
||||
number: u64,
|
||||
consensus: ConsensusV1,
|
||||
}
|
||||
|
||||
/// A header for a block, as needed by Substrate.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, sp_runtime::Serialize)]
|
||||
pub enum SubstrateHeader {
|
||||
/// A version 1 header.
|
||||
V1(SubstrateHeaderV1),
|
||||
}
|
||||
|
||||
impl From<&SubstrateHeader> for Header {
|
||||
fn from(header: &SubstrateHeader) -> Self {
|
||||
match header {
|
||||
SubstrateHeader::V1(header) => {
|
||||
let mut pre_execution_digest = None;
|
||||
let mut execution_digest = None;
|
||||
for log in header.consensus.digest.logs() {
|
||||
match log {
|
||||
DigestItem::PreRuntime(consensus, encoded)
|
||||
if *consensus == SeraiExecutionDigest::CONSENSUS_ID =>
|
||||
{
|
||||
pre_execution_digest =
|
||||
SeraiPreExecutionDigest::deserialize_reader(&mut encoded.as_slice()).ok();
|
||||
}
|
||||
DigestItem::Consensus(consensus, encoded)
|
||||
if *consensus == SeraiExecutionDigest::CONSENSUS_ID =>
|
||||
{
|
||||
execution_digest =
|
||||
SeraiExecutionDigest::deserialize_reader(&mut encoded.as_slice()).ok();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Header::V1(HeaderV1 {
|
||||
number: header.number,
|
||||
builds_upon: execution_digest
|
||||
.as_ref()
|
||||
.map(|digest| digest.builds_upon)
|
||||
.unwrap_or(UnbalancedMerkleTree::EMPTY),
|
||||
unix_time_in_millis: pre_execution_digest
|
||||
.as_ref()
|
||||
.map(|digest| digest.unix_time_in_millis)
|
||||
.unwrap_or(0),
|
||||
transactions_commitment: execution_digest
|
||||
.as_ref()
|
||||
.map(|digest| digest.transactions_commitment)
|
||||
.unwrap_or(UnbalancedMerkleTree::EMPTY),
|
||||
events_commitment: execution_digest
|
||||
.as_ref()
|
||||
.map(|digest| digest.events_commitment)
|
||||
.unwrap_or(UnbalancedMerkleTree::EMPTY),
|
||||
consensus_commitment: sp_core::blake2_256(&header.consensus.encode()),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A block, as needed by Substrate.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, sp_runtime::Serialize)]
|
||||
pub struct SubstrateBlock {
|
||||
header: SubstrateHeader,
|
||||
#[serde(skip)] // This makes this unsafe to deserialize, but we don't impl `Deserialize`
|
||||
transactions: Vec<Transaction>,
|
||||
}
|
||||
|
||||
impl HeaderTrait for SubstrateHeader {
|
||||
type Number = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = sp_runtime::traits::BlakeTwo256;
|
||||
|
||||
fn new(
|
||||
number: Self::Number,
|
||||
extrinsics_root: Self::Hash,
|
||||
state_root: Self::Hash,
|
||||
parent_hash: Self::Hash,
|
||||
digest: Digest,
|
||||
) -> Self {
|
||||
SubstrateHeader::V1(SubstrateHeaderV1 {
|
||||
number,
|
||||
consensus: ConsensusV1 {
|
||||
parent_hash,
|
||||
transactions_root: extrinsics_root,
|
||||
state_root,
|
||||
digest,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn number(&self) -> &Self::Number {
|
||||
match self {
|
||||
SubstrateHeader::V1(SubstrateHeaderV1 { number, .. }) => number,
|
||||
}
|
||||
}
|
||||
fn set_number(&mut self, number: Self::Number) {
|
||||
match self {
|
||||
SubstrateHeader::V1(SubstrateHeaderV1 { number: existing, .. }) => {
|
||||
*existing = number;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extrinsics_root(&self) -> &Self::Hash {
|
||||
match self {
|
||||
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => &consensus.transactions_root,
|
||||
}
|
||||
}
|
||||
fn set_extrinsics_root(&mut self, extrinsics_root: Self::Hash) {
|
||||
match self {
|
||||
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => {
|
||||
consensus.transactions_root = extrinsics_root;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn state_root(&self) -> &Self::Hash {
|
||||
match self {
|
||||
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => &consensus.state_root,
|
||||
}
|
||||
}
|
||||
fn set_state_root(&mut self, state_root: Self::Hash) {
|
||||
match self {
|
||||
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => {
|
||||
consensus.state_root = state_root;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_hash(&self) -> &Self::Hash {
|
||||
match self {
|
||||
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => &consensus.parent_hash,
|
||||
}
|
||||
}
|
||||
fn set_parent_hash(&mut self, parent_hash: Self::Hash) {
|
||||
match self {
|
||||
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => {
|
||||
consensus.parent_hash = parent_hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn digest(&self) -> &Digest {
|
||||
match self {
|
||||
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => &consensus.digest,
|
||||
}
|
||||
}
|
||||
fn digest_mut(&mut self) -> &mut Digest {
|
||||
match self {
|
||||
SubstrateHeader::V1(SubstrateHeaderV1 { consensus, .. }) => &mut consensus.digest,
|
||||
}
|
||||
}
|
||||
|
||||
fn hash(&self) -> H256 {
|
||||
H256::from(Header::from(self).hash().0)
|
||||
}
|
||||
}
|
||||
|
||||
impl HeaderProvider for SubstrateBlock {
|
||||
type HeaderT = SubstrateHeader;
|
||||
}
|
||||
|
||||
impl BlockTrait for SubstrateBlock {
|
||||
type Extrinsic = Transaction;
|
||||
type Header = SubstrateHeader;
|
||||
type Hash = H256;
|
||||
fn header(&self) -> &Self::Header {
|
||||
&self.header
|
||||
}
|
||||
fn extrinsics(&self) -> &[Self::Extrinsic] {
|
||||
&self.transactions
|
||||
}
|
||||
fn deconstruct(self) -> (Self::Header, Vec<Self::Extrinsic>) {
|
||||
(self.header, self.transactions)
|
||||
}
|
||||
fn new(header: Self::Header, transactions: Vec<Self::Extrinsic>) -> Self {
|
||||
Self { header, transactions }
|
||||
}
|
||||
fn encode_from(header: &Self::Header, transactions: &[Self::Extrinsic]) -> Vec<u8> {
|
||||
let header = header.encode();
|
||||
let transactions = transactions.encode();
|
||||
let mut block = header;
|
||||
block.extend(transactions);
|
||||
block
|
||||
}
|
||||
fn hash(&self) -> Self::Hash {
|
||||
self.header.hash()
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "substrate")]
|
||||
pub use substrate::*;
|
||||
@@ -1,25 +1,70 @@
|
||||
use serai_primitives::{Balance, SeraiAddress};
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
pub use serai_coins_primitives as primitives;
|
||||
use primitives::OutInstructionWithBalance;
|
||||
use serai_primitives::{
|
||||
address::SeraiAddress, balance::Balance, instructions::OutInstructionWithBalance,
|
||||
};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
/// A call to coins.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Call {
|
||||
transfer { to: SeraiAddress, balance: Balance },
|
||||
burn { balance: Balance },
|
||||
burn_with_instruction { instruction: OutInstructionWithBalance },
|
||||
/// Transfer these coins to the specified address.
|
||||
transfer {
|
||||
/// The address to transfer to.
|
||||
to: SeraiAddress,
|
||||
/// The coins to transfer.
|
||||
coins: Balance,
|
||||
},
|
||||
/// Burn these coins.
|
||||
burn {
|
||||
/// The coins to burn.
|
||||
coins: Balance,
|
||||
},
|
||||
/// Burn these coins with an `OutInstruction` specified.
|
||||
burn_with_instruction {
|
||||
/// The `OutInstruction`, with the coins to burn.
|
||||
instruction: OutInstructionWithBalance,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
pub enum Event {
|
||||
Mint { to: SeraiAddress, balance: Balance },
|
||||
Burn { from: SeraiAddress, balance: Balance },
|
||||
BurnWithInstruction { from: SeraiAddress, instruction: OutInstructionWithBalance },
|
||||
Transfer { from: SeraiAddress, to: SeraiAddress, balance: Balance },
|
||||
impl Call {
|
||||
pub(crate) fn is_signed(&self) -> bool {
|
||||
match self {
|
||||
Call::transfer { .. } | Call::burn { .. } | Call::burn_with_instruction { .. } => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An event from the system.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Event {
|
||||
/// The specified coins were minted.
|
||||
Mint {
|
||||
/// The address minted to.
|
||||
to: SeraiAddress,
|
||||
/// The coins minted.
|
||||
coins: Balance,
|
||||
},
|
||||
/// The specified coins were burnt.
|
||||
Burn {
|
||||
/// The address burnt from.
|
||||
from: SeraiAddress,
|
||||
/// The coins burnt.
|
||||
coins: Balance,
|
||||
},
|
||||
/// The specified coins were burnt with an `OutInstruction` specified.
|
||||
BurnWithInstruction {
|
||||
/// The address burnt from.
|
||||
from: SeraiAddress,
|
||||
/// The `OutInstruction` specified, and the coins burnt.
|
||||
instruction: OutInstructionWithBalance,
|
||||
},
|
||||
/// The specified coins were transferred.
|
||||
Transfer {
|
||||
/// The address transferred from.
|
||||
from: SeraiAddress,
|
||||
/// The address transferred to.
|
||||
to: SeraiAddress,
|
||||
/// The coins transferred.
|
||||
coins: Balance,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,75 +1,121 @@
|
||||
use sp_runtime::BoundedVec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use serai_primitives::*;
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
type PoolId = ExternalCoin;
|
||||
type MaxSwapPathLength = sp_core::ConstU32<3>;
|
||||
use serai_primitives::{
|
||||
address::SeraiAddress,
|
||||
coin::ExternalCoin,
|
||||
balance::{Amount, ExternalBalance, Balance},
|
||||
};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
/// A call to the DEX.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Call {
|
||||
/// Add liquidity.
|
||||
add_liquidity {
|
||||
/// The coin to add liquidity for.
|
||||
coin: ExternalCoin,
|
||||
coin_desired: SubstrateAmount,
|
||||
sri_desired: SubstrateAmount,
|
||||
coin_min: SubstrateAmount,
|
||||
sri_min: SubstrateAmount,
|
||||
mint_to: SeraiAddress,
|
||||
/// The intended amount of SRI to add as liquidity.
|
||||
sri_intended: Amount,
|
||||
/// The intended amount of the coin to add as liquidity.
|
||||
coin_intended: Amount,
|
||||
/// The minimum amount of SRI to add as liquidity.
|
||||
sri_minimum: Amount,
|
||||
/// The minimum amount of the coin to add as liquidity.
|
||||
coin_minimum: Amount,
|
||||
},
|
||||
/// Transfer these liquidity tokens to the specified address.
|
||||
transfer_liquidity {
|
||||
/// The address to transfer to.
|
||||
to: SeraiAddress,
|
||||
/// The liquidity tokens to transfer.
|
||||
liquidity_tokens: ExternalBalance,
|
||||
},
|
||||
/// Remove liquidity.
|
||||
remove_liquidity {
|
||||
coin: ExternalCoin,
|
||||
lp_token_burn: SubstrateAmount,
|
||||
coin_min_receive: SubstrateAmount,
|
||||
sri_min_receive: SubstrateAmount,
|
||||
withdraw_to: SeraiAddress,
|
||||
/// The liquidity tokens to burn, removing the underlying liquidity from the pool.
|
||||
///
|
||||
/// The `coin` within the balance is the coin to remove liquidity for.
|
||||
liquidity_tokens: ExternalBalance,
|
||||
/// The minimum amount of SRI to receive.
|
||||
sri_minimum: Amount,
|
||||
/// The minimum amount of the coin to receive.
|
||||
coin_minimum: Amount,
|
||||
},
|
||||
swap_exact_tokens_for_tokens {
|
||||
path: BoundedVec<Coin, MaxSwapPathLength>,
|
||||
amount_in: SubstrateAmount,
|
||||
amount_out_min: SubstrateAmount,
|
||||
send_to: SeraiAddress,
|
||||
/// Swap an exact amount of coins.
|
||||
swap_exact {
|
||||
/// The coins to swap.
|
||||
coins_to_swap: Balance,
|
||||
/// The minimum balance to receive.
|
||||
minimum_to_receive: Balance,
|
||||
},
|
||||
swap_tokens_for_exact_tokens {
|
||||
path: BoundedVec<Coin, MaxSwapPathLength>,
|
||||
amount_out: SubstrateAmount,
|
||||
amount_in_max: SubstrateAmount,
|
||||
send_to: SeraiAddress,
|
||||
/// Swap for an exact amount of coins.
|
||||
swap_for_exact {
|
||||
/// The coins to receive.
|
||||
coins_to_receive: Balance,
|
||||
/// The maximum amount to swap.
|
||||
maximum_to_swap: Balance,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
impl Call {
|
||||
pub(crate) fn is_signed(&self) -> bool {
|
||||
match self {
|
||||
Call::add_liquidity { .. } |
|
||||
Call::transfer_liquidity { .. } |
|
||||
Call::remove_liquidity { .. } |
|
||||
Call::swap_exact { .. } |
|
||||
Call::swap_for_exact { .. } => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An event from the DEX.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Event {
|
||||
PoolCreated {
|
||||
pool_id: PoolId,
|
||||
pool_account: SeraiAddress,
|
||||
},
|
||||
|
||||
/// Liquidity was added to a pool.
|
||||
LiquidityAdded {
|
||||
who: SeraiAddress,
|
||||
mint_to: SeraiAddress,
|
||||
pool_id: PoolId,
|
||||
coin_amount: SubstrateAmount,
|
||||
sri_amount: SubstrateAmount,
|
||||
lp_token_minted: SubstrateAmount,
|
||||
/// The account which added the liquidity.
|
||||
origin: SeraiAddress,
|
||||
/// The account which received the liquidity tokens.
|
||||
recipient: SeraiAddress,
|
||||
/// The pool liquidity was added to.
|
||||
pool: ExternalCoin,
|
||||
/// The amount of liquidity tokens which were minted.
|
||||
liquidity_tokens_minted: Amount,
|
||||
/// The amount of the coin which was added to the pool's liquidity.
|
||||
coin_amount: Amount,
|
||||
/// The amount of SRI which was added to the pool's liquidity.
|
||||
sri_amount: Amount,
|
||||
},
|
||||
|
||||
/// Liquidity was removed from a pool.
|
||||
LiquidityRemoved {
|
||||
who: SeraiAddress,
|
||||
withdraw_to: SeraiAddress,
|
||||
pool_id: PoolId,
|
||||
coin_amount: SubstrateAmount,
|
||||
sri_amount: SubstrateAmount,
|
||||
lp_token_burned: SubstrateAmount,
|
||||
/// The account which removed the liquidity.
|
||||
origin: SeraiAddress,
|
||||
/// The pool liquidity was removed from.
|
||||
pool: ExternalCoin,
|
||||
/// The mount of liquidity tokens which were burnt.
|
||||
liquidity_tokens_burnt: Amount,
|
||||
/// The amount of the coin which was removed from the pool's liquidity.
|
||||
coin_amount: Amount,
|
||||
/// The amount of SRI which was removed from the pool's liquidity.
|
||||
sri_amount: Amount,
|
||||
},
|
||||
|
||||
SwapExecuted {
|
||||
who: SeraiAddress,
|
||||
send_to: SeraiAddress,
|
||||
path: BoundedVec<Coin, MaxSwapPathLength>,
|
||||
amount_in: SubstrateAmount,
|
||||
amount_out: SubstrateAmount,
|
||||
/// A swap through the liquidity pools occurred.
|
||||
Swap {
|
||||
/// The account which made the swap.
|
||||
origin: SeraiAddress,
|
||||
/// The recipient for the output of the swap.
|
||||
recipient: SeraiAddress,
|
||||
/// The deltas incurred by the pools.
|
||||
///
|
||||
/// For a swap of sriABC to sriDEF, this would be
|
||||
/// `[Balance { sriABC, 1 }, Balance { SRI, 2 }, Balance { sriDEF, 3 }]`, where
|
||||
/// `Balance { sriABC, 1 }` was added to the `sriABC-SRI` pool, `Balance { SRI, 2 }` was
|
||||
/// removed from the `sriABC-SRI` pool and added to the `sriDEF-SRI` pool, and
|
||||
/// `Balance { sriDEF, 3 }` was removed from the `sriDEF-SRI` pool.
|
||||
deltas: Vec<Balance>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
use serai_primitives::ExternalNetworkId;
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
use serai_primitives::network_id::ExternalNetworkId;
|
||||
|
||||
/// An event from economic security.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Event {
|
||||
EconomicSecurityReached { network: ExternalNetworkId },
|
||||
/// Economic security was achieved for a network's validator set.
|
||||
EconomicSecurityAchieved {
|
||||
/// The network whose validator set achieved economic security.
|
||||
network: ExternalNetworkId,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pub use serai_emissions_primitives as primitives;
|
||||
@@ -1,20 +1,50 @@
|
||||
pub use serai_genesis_liquidity_primitives as primitives;
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
use serai_primitives::*;
|
||||
use primitives::*;
|
||||
use serai_primitives::{
|
||||
crypto::Signature, address::SeraiAddress, balance::ExternalBalance, genesis::GenesisValues,
|
||||
};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
/// A call to the genesis liquidity.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Call {
|
||||
remove_coin_liquidity { balance: ExternalBalance },
|
||||
oraclize_values { values: Values, signature: Signature },
|
||||
/// Oraclize the value of non-Bitcoin external coins relative to Bitcoin.
|
||||
oraclize_values {
|
||||
/// The values of the non-Bitcoin external coins.
|
||||
values: GenesisValues,
|
||||
/// The signature by the genesis validators for these values.
|
||||
signature: Signature,
|
||||
},
|
||||
/// Remove liquidity.
|
||||
remove_liquidity {
|
||||
/// The genesis liquidity to remove.
|
||||
balance: ExternalBalance,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Event {
|
||||
GenesisLiquidityAdded { by: SeraiAddress, balance: ExternalBalance },
|
||||
GenesisLiquidityRemoved { by: SeraiAddress, balance: ExternalBalance },
|
||||
GenesisLiquidityAddedToPool { coin: ExternalBalance, sri: Amount },
|
||||
impl Call {
|
||||
pub(crate) fn is_signed(&self) -> bool {
|
||||
match self {
|
||||
Call::oraclize_values { .. } => false,
|
||||
Call::remove_liquidity { .. } => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An event from the genesis liquidity.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Event {
|
||||
/// Genesis liquidity added.
|
||||
GenesisLiquidityAdded {
|
||||
/// The recipient of the genesis liquidity.
|
||||
recipient: SeraiAddress,
|
||||
/// The coins added as genesis liquidity.
|
||||
balance: ExternalBalance,
|
||||
},
|
||||
/// Genesis liquidity removed.
|
||||
GenesisLiquidityRemoved {
|
||||
/// The account which removed the genesis liquidity.
|
||||
origin: SeraiAddress,
|
||||
/// The amount of genesis liquidity removed.
|
||||
balance: ExternalBalance,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
use sp_consensus_grandpa::EquivocationProof;
|
||||
|
||||
use serai_primitives::{BlockNumber, SeraiAddress};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
pub struct ReportEquivocation {
|
||||
pub equivocation_proof: alloc::boxed::Box<EquivocationProof<[u8; 32], BlockNumber>>,
|
||||
pub key_owner_proof: SeraiAddress,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
pub enum Call {
|
||||
report_equivocation(ReportEquivocation),
|
||||
report_equivocation_unsigned(ReportEquivocation),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
pub enum Event {
|
||||
NewAuthorities { authority_set: alloc::vec::Vec<(SeraiAddress, u64)> },
|
||||
// TODO: Remove these
|
||||
Paused,
|
||||
Resumed,
|
||||
}
|
||||
@@ -1,30 +1,47 @@
|
||||
use serai_primitives::*;
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
pub use serai_in_instructions_primitives as primitives;
|
||||
use primitives::SignedBatch;
|
||||
use serai_validator_sets_primitives::Session;
|
||||
use serai_primitives::{
|
||||
BlockHash, network_id::ExternalNetworkId, validator_sets::Session, instructions::SignedBatch,
|
||||
};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
/// A call to `InInstruction`s.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Call {
|
||||
execute_batch { batch: SignedBatch },
|
||||
/// Execute a batch of `InInstruction`s.
|
||||
execute_batch {
|
||||
/// The batch to execute.
|
||||
batch: SignedBatch,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
impl Call {
|
||||
pub(crate) fn is_signed(&self) -> bool {
|
||||
match self {
|
||||
Call::execute_batch { .. } => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An event from `InInstruction`s.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Event {
|
||||
/// A batch of `InInstruction`s was executed.
|
||||
Batch {
|
||||
/// The network for which a batch was executed.
|
||||
network: ExternalNetworkId,
|
||||
/// The session which published the batch.
|
||||
publishing_session: Session,
|
||||
/// The ID of the batch.
|
||||
id: u32,
|
||||
/// The hash of the block on the external network which caused this batch's creation.
|
||||
external_network_block_hash: BlockHash,
|
||||
/// The hash of the `InInstruction`s within this batch.
|
||||
in_instructions_hash: [u8; 32],
|
||||
/// The results of each `InInstruction` within the batch.
|
||||
#[borsh(
|
||||
serialize_with = "serai_primitives::sp_borsh::borsh_serialize_bitvec",
|
||||
deserialize_with = "serai_primitives::sp_borsh::borsh_deserialize_bitvec"
|
||||
)]
|
||||
in_instruction_results: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>,
|
||||
},
|
||||
Halt {
|
||||
network: ExternalNetworkId,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,94 +1,98 @@
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
pub use serai_primitives as primitives;
|
||||
|
||||
/// Call/Event for the system.
|
||||
pub mod system;
|
||||
|
||||
pub mod timestamp;
|
||||
|
||||
/// Call/Event for coins.
|
||||
pub mod coins;
|
||||
pub mod liquidity_tokens;
|
||||
pub mod dex;
|
||||
|
||||
/// Call/Event for validator sets.
|
||||
pub mod validator_sets;
|
||||
|
||||
pub mod genesis_liquidity;
|
||||
pub mod emissions;
|
||||
|
||||
pub mod economic_security;
|
||||
|
||||
pub mod in_instructions;
|
||||
|
||||
/// Call/Event for signals.
|
||||
pub mod signals;
|
||||
|
||||
pub mod babe;
|
||||
pub mod grandpa;
|
||||
/// Call/Event for the DEX.
|
||||
pub mod dex;
|
||||
|
||||
pub mod tx;
|
||||
/// Call/Event for genesis liquidity.
|
||||
pub mod genesis_liquidity;
|
||||
/// Event for economic security.
|
||||
pub mod economic_security;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
/// Call/Event for `InInstruction`s.
|
||||
pub mod in_instructions;
|
||||
|
||||
mod transaction;
|
||||
pub use transaction::*;
|
||||
|
||||
mod block;
|
||||
pub use block::*;
|
||||
|
||||
/// All calls.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
#[borsh(use_discriminant = true)]
|
||||
#[repr(u8)]
|
||||
pub enum Call {
|
||||
Timestamp(timestamp::Call),
|
||||
Coins(coins::Call),
|
||||
LiquidityTokens(liquidity_tokens::Call),
|
||||
Dex(dex::Call),
|
||||
ValidatorSets(validator_sets::Call),
|
||||
GenesisLiquidity(genesis_liquidity::Call),
|
||||
InInstructions(in_instructions::Call),
|
||||
Signals(signals::Call),
|
||||
Babe(babe::Call),
|
||||
Grandpa(grandpa::Call),
|
||||
// The call for the system.
|
||||
// System(system::Call) = 0,
|
||||
/// The call for coins.
|
||||
Coins(coins::Call) = 1,
|
||||
/// The call for validator sets.
|
||||
ValidatorSets(validator_sets::Call) = 2,
|
||||
/// The call for signals.
|
||||
Signals(signals::Call) = 3,
|
||||
/// The call for the DEX.
|
||||
Dex(dex::Call) = 4,
|
||||
/// The call for genesis liquidity.
|
||||
GenesisLiquidity(genesis_liquidity::Call) = 5,
|
||||
// The call for economic security.
|
||||
// EconomicSecurity = 6,
|
||||
/// The call for `InInstruction`s.
|
||||
InInstructions(in_instructions::Call) = 7,
|
||||
}
|
||||
|
||||
// TODO: Remove this
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
pub enum TransactionPaymentEvent {
|
||||
TransactionFeePaid { who: serai_primitives::SeraiAddress, actual_fee: u64, tip: u64 },
|
||||
impl Call {
|
||||
pub(crate) fn is_signed(&self) -> bool {
|
||||
match self {
|
||||
Call::Coins(call) => call.is_signed(),
|
||||
Call::ValidatorSets(call) => call.is_signed(),
|
||||
Call::Signals(call) => call.is_signed(),
|
||||
Call::Dex(call) => call.is_signed(),
|
||||
Call::GenesisLiquidity(call) => call.is_signed(),
|
||||
Call::InInstructions(call) => call.is_signed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
/// All events.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
#[borsh(use_discriminant = true)]
|
||||
#[repr(u8)]
|
||||
pub enum Event {
|
||||
System(system::Event),
|
||||
Timestamp,
|
||||
TransactionPayment(TransactionPaymentEvent),
|
||||
Coins(coins::Event),
|
||||
LiquidityTokens(liquidity_tokens::Event),
|
||||
Dex(dex::Event),
|
||||
ValidatorSets(validator_sets::Event),
|
||||
GenesisLiquidity(genesis_liquidity::Event),
|
||||
Emissions,
|
||||
EconomicSecurity(economic_security::Event),
|
||||
InInstructions(in_instructions::Event),
|
||||
Signals(signals::Event),
|
||||
Babe,
|
||||
Grandpa(grandpa::Event),
|
||||
/// The event for the system.
|
||||
System(system::Event) = 0,
|
||||
/// The event for coins.
|
||||
Coins(coins::Event) = 1,
|
||||
/// The event for validator sets.
|
||||
ValidatorSets(validator_sets::Event) = 2,
|
||||
/// The event for signals.
|
||||
Signals(signals::Event) = 3,
|
||||
/// The event for the DEX.
|
||||
Dex(dex::Event) = 4,
|
||||
/// The event for genesis liquidity.
|
||||
GenesisLiquidity(genesis_liquidity::Event) = 5,
|
||||
/// The event for economic security.
|
||||
EconomicSecurity(economic_security::Event) = 6,
|
||||
/// The event for `InInstruction`s.
|
||||
InInstructions(in_instructions::Event) = 7,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
pub struct Extra {
|
||||
pub era: sp_runtime::generic::Era,
|
||||
#[codec(compact)]
|
||||
pub nonce: u32,
|
||||
#[codec(compact)]
|
||||
pub tip: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
pub struct SignedPayloadExtra {
|
||||
pub spec_version: u32,
|
||||
pub tx_version: u32,
|
||||
pub genesis: [u8; 32],
|
||||
pub mortality_checkpoint: [u8; 32],
|
||||
}
|
||||
|
||||
pub type Transaction = tx::Transaction<Call, Extra>;
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
use serai_primitives::{Balance, SeraiAddress};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Call {
|
||||
burn { balance: Balance },
|
||||
transfer { to: SeraiAddress, balance: Balance },
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Event {
|
||||
Mint { to: SeraiAddress, balance: Balance },
|
||||
Burn { from: SeraiAddress, balance: Balance },
|
||||
Transfer { from: SeraiAddress, to: SeraiAddress, balance: Balance },
|
||||
}
|
||||
@@ -1,59 +1,132 @@
|
||||
use serai_primitives::{NetworkId, SeraiAddress};
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
use serai_validator_sets_primitives::ValidatorSet;
|
||||
use serai_primitives::{
|
||||
address::SeraiAddress, network_id::NetworkId, validator_sets::ValidatorSet, signals::Signal,
|
||||
};
|
||||
|
||||
pub use serai_signals_primitives as primitives;
|
||||
use primitives::SignalId;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
/// A call to signals.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Call {
|
||||
register_retirement_signal { in_favor_of: [u8; 32] },
|
||||
revoke_retirement_signal { retirement_signal_id: [u8; 32] },
|
||||
favor { signal_id: SignalId, for_network: NetworkId },
|
||||
revoke_favor { signal_id: SignalId, for_network: NetworkId },
|
||||
stand_against { signal_id: SignalId, for_network: NetworkId },
|
||||
/// Register a retirement signal.
|
||||
register_retirement_signal {
|
||||
/// The protocol favored over the current protocol.
|
||||
in_favor_of: [u8; 32],
|
||||
},
|
||||
/// Revoke a retirement signal.
|
||||
revoke_retirement_signal {
|
||||
/// The protocol which was favored over the current protocol
|
||||
was_in_favor_of: [u8; 32],
|
||||
},
|
||||
/// Favor a signal.
|
||||
favor {
|
||||
/// The signal to favor.
|
||||
signal: Signal,
|
||||
/// The network this validator is expressing favor with.
|
||||
///
|
||||
/// A validator may be an active validator for multiple networks. The validator must specify
|
||||
/// which network they're expressing favor with in this call.
|
||||
with_network: NetworkId,
|
||||
},
|
||||
/// Revoke favor for a signal.
|
||||
revoke_favor {
|
||||
/// The signal to revoke favor for.
|
||||
signal: Signal,
|
||||
/// The network this validator is revoking favor with.
|
||||
///
|
||||
/// A validator may have expressed favor with multiple networks. The validator must specify
|
||||
/// which network they're revoking favor with in this call.
|
||||
with_network: NetworkId,
|
||||
},
|
||||
/// Stand against a signal.
|
||||
///
|
||||
/// This has no effects other than emitting an event that this signal is stood against. If the
|
||||
/// origin has prior expressed favor, they must still call `revoke_favor` for each network they
|
||||
/// expressed favor with.
|
||||
stand_against {
|
||||
/// The signal to stand against.
|
||||
signal: Signal,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
impl Call {
|
||||
pub(crate) fn is_signed(&self) -> bool {
|
||||
match self {
|
||||
Call::register_retirement_signal { .. } |
|
||||
Call::revoke_retirement_signal { .. } |
|
||||
Call::favor { .. } |
|
||||
Call::revoke_favor { .. } |
|
||||
Call::stand_against { .. } => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An event from signals.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Event {
|
||||
/// A retirement signal has been registered.
|
||||
RetirementSignalRegistered {
|
||||
signal_id: [u8; 32],
|
||||
/// The retirement signal's ID.
|
||||
signal: [u8; 32],
|
||||
/// The protocol retirement is proposed in favor of.
|
||||
in_favor_of: [u8; 32],
|
||||
/// The address which registered this signal.
|
||||
registrant: SeraiAddress,
|
||||
},
|
||||
/// A retirement signal was revoked.
|
||||
RetirementSignalRevoked {
|
||||
signal_id: [u8; 32],
|
||||
/// The retirement signal's ID.
|
||||
signal: [u8; 32],
|
||||
},
|
||||
/// A signal was favored.
|
||||
SignalFavored {
|
||||
signal_id: SignalId,
|
||||
/// The signal favored.
|
||||
signal: Signal,
|
||||
/// The validator the signal was favored by.
|
||||
by: SeraiAddress,
|
||||
for_network: NetworkId,
|
||||
},
|
||||
SetInFavor {
|
||||
signal_id: SignalId,
|
||||
set: ValidatorSet,
|
||||
},
|
||||
RetirementSignalLockedIn {
|
||||
signal_id: [u8; 32],
|
||||
},
|
||||
SetNoLongerInFavor {
|
||||
signal_id: SignalId,
|
||||
set: ValidatorSet,
|
||||
/// The network with which the signal was favored.
|
||||
with_network: NetworkId,
|
||||
},
|
||||
/// Favor for a signal was revoked.
|
||||
FavorRevoked {
|
||||
signal_id: SignalId,
|
||||
/// The signal whose favor was revoked.
|
||||
signal: Signal,
|
||||
/// The validator who revoked their favor for the signal.
|
||||
by: SeraiAddress,
|
||||
for_network: NetworkId,
|
||||
/// The network with which favor for the signal was revoked.
|
||||
with_network: NetworkId,
|
||||
},
|
||||
/// A supermajority of a validator set now favor a signal.
|
||||
SetInFavor {
|
||||
/// The signal which now has a supermajority of a validator set favoring it.
|
||||
signal: Signal,
|
||||
/// The validator set which is now considered to favor the signal.
|
||||
set: ValidatorSet,
|
||||
},
|
||||
/// A validator set is no longer considered to favor a signal.
|
||||
SetNoLongerInFavor {
|
||||
/// The signal which no longer has the validator set considered in favor of it.
|
||||
signal: Signal,
|
||||
/// The validator set which is no longer considered to be in favor of the signal.
|
||||
set: ValidatorSet,
|
||||
},
|
||||
/// A retirement signal has been locked in.
|
||||
RetirementSignalLockedIn {
|
||||
/// The signal which has been locked in.
|
||||
signal: [u8; 32],
|
||||
},
|
||||
/// A validator set's ability to publish batches was halted.
|
||||
///
|
||||
/// This also halts set rotation in effect, as handovers are via new sets starting to publish
|
||||
/// batches.
|
||||
SetHalted {
|
||||
/// The signal which has been locked in.
|
||||
signal: [u8; 32],
|
||||
},
|
||||
/// An account has stood against a signal.
|
||||
AgainstSignal {
|
||||
signal_id: SignalId,
|
||||
/// The signal stood against.
|
||||
signal: Signal,
|
||||
/// The account which stood against the signal.
|
||||
who: SeraiAddress,
|
||||
for_network: NetworkId,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use frame_support::dispatch::{DispatchInfo, DispatchError};
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
use serai_primitives::SeraiAddress;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
/// An event from the system.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Event {
|
||||
ExtrinsicSuccess { dispatch_info: DispatchInfo },
|
||||
ExtrinsicFailed { dispatch_error: DispatchError, dispatch_info: DispatchInfo },
|
||||
CodeUpdated,
|
||||
NewAccount { account: SeraiAddress },
|
||||
KilledAccount { account: SeraiAddress },
|
||||
Remarked { sender: SeraiAddress, hash: [u8; 32] },
|
||||
/// The transaction successfully executed.
|
||||
TransactionSuccess,
|
||||
/// The transaction failed to execute.
|
||||
// TODO: Add an error to this
|
||||
TransactionFailed,
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
pub enum Call {
|
||||
set {
|
||||
#[codec(compact)]
|
||||
now: u64,
|
||||
},
|
||||
}
|
||||
563
substrate/abi/src/transaction.rs
Normal file
563
substrate/abi/src/transaction.rs
Normal file
@@ -0,0 +1,563 @@
|
||||
use core::num::NonZero;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use borsh::{io, BorshSerialize, BorshDeserialize};
|
||||
|
||||
use sp_core::{ConstU32, bounded::BoundedVec};
|
||||
use serai_primitives::{BlockHash, address::SeraiAddress, balance::Amount, crypto::Signature};
|
||||
use crate::Call;
|
||||
|
||||
/// The maximum amount of calls allowed in a transaction.
|
||||
pub const MAX_CALLS: u32 = 8;
|
||||
|
||||
/// An error regarding `SignedCalls`.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum SignedCallsError {
|
||||
/// No calls were included.
|
||||
NoCalls,
|
||||
/// Too many calls were included.
|
||||
TooManyCalls,
|
||||
/// An unsigned call was included.
|
||||
IncludedUnsignedCall,
|
||||
}
|
||||
|
||||
/// A `Vec` of signed calls.
|
||||
// We don't implement BorshDeserialize due to to maintained invariants on this struct.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize)]
|
||||
pub struct SignedCalls(
|
||||
#[borsh(serialize_with = "serai_primitives::sp_borsh::borsh_serialize_bounded_vec")]
|
||||
BoundedVec<Call, ConstU32<{ MAX_CALLS }>>,
|
||||
);
|
||||
impl TryFrom<Vec<Call>> for SignedCalls {
|
||||
type Error = SignedCallsError;
|
||||
fn try_from(calls: Vec<Call>) -> Result<Self, Self::Error> {
|
||||
if calls.is_empty() {
|
||||
Err(SignedCallsError::NoCalls)?;
|
||||
}
|
||||
for call in &calls {
|
||||
if !call.is_signed() {
|
||||
Err(SignedCallsError::IncludedUnsignedCall)?;
|
||||
}
|
||||
}
|
||||
calls.try_into().map_err(|_| SignedCallsError::TooManyCalls).map(SignedCalls)
|
||||
}
|
||||
}
|
||||
|
||||
/// An error regarding `UnsignedCall`.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum UnsignedCallError {
|
||||
/// A signed call was specified.
|
||||
SignedCall,
|
||||
}
|
||||
|
||||
/// An unsigned call.
|
||||
// We don't implement BorshDeserialize due to to maintained invariants on this struct.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize)]
|
||||
pub struct UnsignedCall(Call);
|
||||
impl TryFrom<Call> for UnsignedCall {
|
||||
type Error = UnsignedCallError;
|
||||
fn try_from(call: Call) -> Result<Self, Self::Error> {
|
||||
if call.is_signed() {
|
||||
Err(UnsignedCallError::SignedCall)?;
|
||||
}
|
||||
Ok(UnsignedCall(call))
|
||||
}
|
||||
}
|
||||
|
||||
/// Part of the context used to sign with, from the protocol.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub struct ImplicitContext {
|
||||
/// The genesis hash of the blockchain.
|
||||
pub genesis: BlockHash,
|
||||
/// The ID of the current protocol.
|
||||
pub protocol_id: [u8; 32],
|
||||
}
|
||||
|
||||
/// Part of the context used to sign with, specified within the transaction itself.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub struct ExplicitContext {
|
||||
/// The historic block this transaction builds upon.
|
||||
///
|
||||
/// This transaction can not be included in a blockchain which does not include this block.
|
||||
pub historic_block: BlockHash,
|
||||
|
||||
/// The UNIX time this transaction must be included by (and expires after).
|
||||
///
|
||||
/// This transaction can not be included in a block whose time is equal or greater to this value.
|
||||
pub include_by: Option<NonZero<u64>>,
|
||||
|
||||
/// The signer.
|
||||
pub signer: SeraiAddress,
|
||||
|
||||
/// The signer's nonce.
|
||||
pub nonce: u32,
|
||||
|
||||
/// The fee, in SRI, paid to the network for inclusion.
|
||||
///
|
||||
/// This fee is paid regardless of the success of any of the calls.
|
||||
pub fee: Amount,
|
||||
}
|
||||
|
||||
/// A signature, with context.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub struct ContextualizedSignature {
|
||||
/// The explicit context.
|
||||
explicit_context: ExplicitContext,
|
||||
/// The signature.
|
||||
signature: Signature,
|
||||
}
|
||||
|
||||
/// A Serai transaction.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub enum Transaction {
|
||||
/// An unsigned transaction.
|
||||
Unsigned {
|
||||
/// The contained call.
|
||||
call: UnsignedCall,
|
||||
},
|
||||
/// A signed transaction.
|
||||
Signed {
|
||||
/// The calls.
|
||||
///
|
||||
/// These calls are executed atomically. Either all successfully execute or none do. The
|
||||
/// transaction's fee is paid regardless.
|
||||
calls: SignedCalls,
|
||||
/// The signature for this transaction.
|
||||
///
|
||||
/// This is not checked on deserializtion and may be invalid.
|
||||
contextualized_signature: ContextualizedSignature,
|
||||
},
|
||||
}
|
||||
|
||||
impl BorshSerialize for Transaction {
|
||||
fn serialize<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||
match self {
|
||||
Transaction::Unsigned { call } => {
|
||||
/*
|
||||
`Signed` `Transaction`s encode the length of their `Vec<Call>` here. Since that `Vec` is
|
||||
bound to be non-empty, it will never write `0`, enabling `Unsigned` to use it.
|
||||
|
||||
The benefit to these not overlapping is in the ability to determine if the `Transaction`
|
||||
has a signature or not. If this wrote a `1`, for the amount of `Call`s present in the
|
||||
`Transaction`, that `Call` would have to be introspected for if its signed or not. With
|
||||
the usage of `0`, given how low `MAX_CALLS` is, this `Transaction` can technically be
|
||||
defined as an enum of
|
||||
`0 Call, 1 Call ContextualizedSignature, 2 Call Call ContextualizedSignature ...`, to
|
||||
maintain compatbility with the borsh specification without wrapper functions. The checks
|
||||
here on `Call` types/quantity could be moved to later validation functions.
|
||||
*/
|
||||
writer.write_all(&[0])?;
|
||||
call.serialize(writer)
|
||||
}
|
||||
Transaction::Signed { calls, contextualized_signature } => {
|
||||
serai_primitives::sp_borsh::borsh_serialize_bounded_vec(&calls.0, writer)?;
|
||||
contextualized_signature.serialize(writer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BorshDeserialize for Transaction {
|
||||
fn deserialize_reader<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
||||
let mut len = [0xff];
|
||||
reader.read_exact(&mut len)?;
|
||||
let len = len[0];
|
||||
|
||||
if len == 0 {
|
||||
let call = Call::deserialize_reader(reader)?;
|
||||
if call.is_signed() {
|
||||
Err(io::Error::new(io::ErrorKind::Other, "call was signed but marked unsigned"))?;
|
||||
}
|
||||
Ok(Transaction::Unsigned { call: UnsignedCall(call) })
|
||||
} else {
|
||||
if u32::from(len) > MAX_CALLS {
|
||||
Err(io::Error::new(io::ErrorKind::Other, "too many calls"))?;
|
||||
}
|
||||
let mut calls = BoundedVec::with_bounded_capacity(len.into());
|
||||
for _ in 0 .. len {
|
||||
let call = Call::deserialize_reader(reader)?;
|
||||
if !call.is_signed() {
|
||||
Err(io::Error::new(io::ErrorKind::Other, "call was unsigned but included as signed"))?;
|
||||
}
|
||||
calls.try_push(call).unwrap();
|
||||
}
|
||||
let contextualized_signature = ContextualizedSignature::deserialize_reader(reader)?;
|
||||
Ok(Transaction::Signed { calls: SignedCalls(calls), contextualized_signature })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Transaction {
|
||||
/// The message to sign to produce a signature.
|
||||
pub fn signature_message(
|
||||
calls: &SignedCalls,
|
||||
implicit_context: &ImplicitContext,
|
||||
explicit_context: &ExplicitContext,
|
||||
) -> Vec<u8> {
|
||||
let mut message = Vec::with_capacity(
|
||||
(calls.0.len() * 64) +
|
||||
core::mem::size_of::<ImplicitContext>() +
|
||||
core::mem::size_of::<ExplicitContext>(),
|
||||
);
|
||||
calls.serialize(&mut message).unwrap();
|
||||
implicit_context.serialize(&mut message).unwrap();
|
||||
explicit_context.serialize(&mut message).unwrap();
|
||||
message
|
||||
}
|
||||
|
||||
/// The unique hash of this transaction.
|
||||
///
|
||||
/// No two transactions on the blockchain will share a hash, making this a unique identifier.
|
||||
/// For signed transactions, this is due to the `(signer, nonce)` pair present within the
|
||||
/// `ExplicitContext`. For unsigned transactions, this is due to inherent properties of their
|
||||
/// execution (e.g. only being able to set a `ValidatorSet`'s keys once).
|
||||
pub fn hash(&self) -> [u8; 32] {
|
||||
sp_core::blake2_256(&match self {
|
||||
Transaction::Unsigned { call } => borsh::to_vec(&call).unwrap(),
|
||||
Transaction::Signed {
|
||||
calls,
|
||||
contextualized_signature: ContextualizedSignature { explicit_context, signature: _ },
|
||||
} => {
|
||||
// We explicitly don't hash the signature, so signatures can be replaced in the future if
|
||||
// desired (such as with half-aggregated Schnorr signatures)
|
||||
borsh::to_vec(&(calls, explicit_context)).unwrap()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "substrate")]
|
||||
mod substrate {
|
||||
use core::fmt::Debug;
|
||||
use alloc::vec;
|
||||
|
||||
use scale::{Encode, Decode};
|
||||
use sp_runtime::{
|
||||
transaction_validity::*,
|
||||
traits::{Verify, ExtrinsicLike, Dispatchable, ValidateUnsigned, Checkable, Applyable},
|
||||
Weight,
|
||||
};
|
||||
#[rustfmt::skip]
|
||||
use frame_support::dispatch::{DispatchClass, Pays, DispatchInfo, GetDispatchInfo, PostDispatchInfo};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl Encode for Transaction {
|
||||
fn encode(&self) -> Vec<u8> {
|
||||
borsh::to_vec(self).unwrap()
|
||||
}
|
||||
}
|
||||
impl Decode for Transaction {
|
||||
fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
|
||||
struct ScaleRead<'a, I: scale::Input>(&'a mut I, Option<scale::Error>);
|
||||
impl<I: scale::Input> borsh::io::Read for ScaleRead<'_, I> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> borsh::io::Result<usize> {
|
||||
let remaining_len = self.0.remaining_len().map_err(|err| {
|
||||
self.1 = Some(err);
|
||||
borsh::io::Error::new(borsh::io::ErrorKind::Other, "")
|
||||
})?;
|
||||
// If we're still calling `read`, we try to read at least one more byte
|
||||
let to_read = buf.len().min(remaining_len.unwrap_or(1));
|
||||
self.0.read(&mut buf[.. to_read]).map_err(|err| {
|
||||
self.1 = Some(err);
|
||||
borsh::io::Error::new(borsh::io::ErrorKind::Other, "")
|
||||
})?;
|
||||
Ok(to_read)
|
||||
}
|
||||
}
|
||||
let mut input = ScaleRead(input, None);
|
||||
match Self::deserialize_reader(&mut input) {
|
||||
Ok(res) => Ok(res),
|
||||
Err(_) => Err(input.1.unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The context which transactions are executed in.
|
||||
pub trait TransactionContext: 'static + Send + Sync + Clone + PartialEq + Eq + Debug {
|
||||
/// The base weight for a signed transaction.
|
||||
const SIGNED_WEIGHT: Weight;
|
||||
|
||||
/// The call type for the runtime.
|
||||
type RuntimeCall: From<Call>
|
||||
+ GetDispatchInfo
|
||||
+ Dispatchable<
|
||||
RuntimeOrigin: From<Option<SeraiAddress>>,
|
||||
Info = DispatchInfo,
|
||||
PostInfo = PostDispatchInfo,
|
||||
>;
|
||||
|
||||
/// The implicit context to verify transactions with.
|
||||
fn implicit_context() -> ImplicitContext;
|
||||
|
||||
/// If a block is present in the blockchain.
|
||||
fn block_is_present_in_blockchain(&self, hash: &BlockHash) -> bool;
|
||||
/// The time embedded into the current block.
|
||||
///
|
||||
/// Returns `None` if the time has yet to be set.
|
||||
fn current_time(&self) -> Option<u64>;
|
||||
/// Get the next nonce for an account.
|
||||
fn next_nonce(&self, signer: &SeraiAddress) -> u32;
|
||||
/// If the signer can pay the SRI fee.
|
||||
fn can_pay_fee(
|
||||
&self,
|
||||
signer: &SeraiAddress,
|
||||
fee: Amount,
|
||||
) -> Result<(), TransactionValidityError>;
|
||||
|
||||
/// Begin execution of a transaction.
|
||||
fn start_transaction(&self);
|
||||
/// Consume the next nonce for an account.
|
||||
fn consume_next_nonce(&self, signer: &SeraiAddress);
|
||||
/// Have the transaction pay its SRI fee.
|
||||
fn pay_fee(&self, signer: &SeraiAddress, fee: Amount) -> Result<(), TransactionValidityError>;
|
||||
/// End execution of a transaction.
|
||||
fn end_transaction(&self, transaction_hash: [u8; 32]);
|
||||
}
|
||||
|
||||
/// A transaction with the context necessary to evaluate it within Substrate.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub struct TransactionWithContext<Context: TransactionContext>(
|
||||
Transaction,
|
||||
#[codec(skip)] Context,
|
||||
);
|
||||
|
||||
impl ExtrinsicLike for Transaction {
|
||||
fn is_signed(&self) -> Option<bool> {
|
||||
Some(matches!(self, Transaction::Signed { .. }))
|
||||
}
|
||||
fn is_bare(&self) -> bool {
|
||||
matches!(self, Transaction::Unsigned { .. })
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context: TransactionContext> GetDispatchInfo for TransactionWithContext<Context> {
|
||||
fn get_dispatch_info(&self) -> DispatchInfo {
|
||||
match &self.0 {
|
||||
Transaction::Unsigned { call } => DispatchInfo {
|
||||
call_weight: Context::RuntimeCall::from(call.0.clone()).get_dispatch_info().call_weight,
|
||||
extension_weight: Weight::zero(),
|
||||
class: DispatchClass::Operational,
|
||||
pays_fee: Pays::No,
|
||||
},
|
||||
Transaction::Signed { calls, .. } => DispatchInfo {
|
||||
call_weight: calls
|
||||
.0
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|call| Context::RuntimeCall::from(call).get_dispatch_info().call_weight)
|
||||
.fold(Weight::zero(), |accum, item| accum + item),
|
||||
extension_weight: Context::SIGNED_WEIGHT,
|
||||
class: DispatchClass::Normal,
|
||||
pays_fee: Pays::Yes,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context: TransactionContext> Checkable<Context> for Transaction {
|
||||
type Checked = TransactionWithContext<Context>;
|
||||
|
||||
fn check(self, context: &Context) -> Result<Self::Checked, TransactionValidityError> {
|
||||
match &self {
|
||||
Transaction::Unsigned { .. } => {}
|
||||
Transaction::Signed {
|
||||
calls,
|
||||
contextualized_signature: ContextualizedSignature { explicit_context, signature },
|
||||
} => {
|
||||
if !sp_core::sr25519::Signature::from(*signature).verify(
|
||||
Transaction::signature_message(calls, &Context::implicit_context(), explicit_context)
|
||||
.as_slice(),
|
||||
&sp_core::sr25519::Public::from(explicit_context.signer),
|
||||
) {
|
||||
Err(InvalidTransaction::BadProof)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(TransactionWithContext(self, context.clone()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn unchecked_into_checked_i_know_what_i_am_doing(
|
||||
self,
|
||||
c: &Context,
|
||||
) -> Result<Self::Checked, TransactionValidityError> {
|
||||
// This satisfies the API, not necessarily the intent, yet this fn is only intended to be used
|
||||
// within tests. Accordingly, it's fine to be stricter than necessarily
|
||||
self.check(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context: TransactionContext> TransactionWithContext<Context> {
|
||||
fn validate_except_fee<V: ValidateUnsigned<Call = Context::RuntimeCall>>(
|
||||
&self,
|
||||
source: TransactionSource,
|
||||
mempool_priority_if_signed: u64,
|
||||
) -> TransactionValidity {
|
||||
match &self.0 {
|
||||
Transaction::Unsigned { call } => {
|
||||
let ValidTransaction { priority: _, requires, provides, longevity: _, propagate: _ } =
|
||||
V::validate_unsigned(source, &Context::RuntimeCall::from(call.0.clone()))?;
|
||||
Ok(ValidTransaction {
|
||||
// We should always try to include unsigned transactions prior to signed
|
||||
priority: u64::MAX,
|
||||
requires,
|
||||
provides,
|
||||
// This is valid until included
|
||||
longevity: u64::MAX,
|
||||
// Ensure this is propagated
|
||||
propagate: true,
|
||||
})
|
||||
}
|
||||
Transaction::Signed { calls: _, contextualized_signature } => {
|
||||
let ExplicitContext { historic_block, include_by, signer, nonce, fee: _ } =
|
||||
&contextualized_signature.explicit_context;
|
||||
if !self.1.block_is_present_in_blockchain(historic_block) {
|
||||
// We don't know if this is a block from a fundamentally distinct blockchain or a
|
||||
// continuation of this blockchain we have yet to sync (which would be `Future`)
|
||||
Err(TransactionValidityError::Unknown(UnknownTransaction::CannotLookup))?;
|
||||
}
|
||||
if let Some(include_by) = *include_by {
|
||||
if let Some(current_time) = self.1.current_time() {
|
||||
if current_time >= u64::from(include_by) {
|
||||
// Since this transaction has a time bound which has passed, error
|
||||
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))?;
|
||||
}
|
||||
} else {
|
||||
// Since this transaction has a time bound, yet we don't know the time, error
|
||||
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))?;
|
||||
}
|
||||
}
|
||||
match self.1.next_nonce(signer).cmp(nonce) {
|
||||
core::cmp::Ordering::Less => {
|
||||
Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))?
|
||||
}
|
||||
core::cmp::Ordering::Equal => {}
|
||||
core::cmp::Ordering::Greater => {
|
||||
Err(TransactionValidityError::Invalid(InvalidTransaction::Future))?
|
||||
}
|
||||
}
|
||||
|
||||
let requires = if let Some(prior_nonce) = nonce.checked_sub(1) {
|
||||
vec![borsh::to_vec(&(signer, prior_nonce)).unwrap()]
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
let provides = vec![borsh::to_vec(&(signer, nonce)).unwrap()];
|
||||
Ok(ValidTransaction {
|
||||
priority: mempool_priority_if_signed,
|
||||
requires,
|
||||
provides,
|
||||
// This revalidates the transaction every block. This is required due to this being
|
||||
// denominated in blocks, and our transaction expiration being denominated in seconds.
|
||||
longevity: 1,
|
||||
propagate: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Context: TransactionContext> Applyable for TransactionWithContext<Context> {
|
||||
type Call = Context::RuntimeCall;
|
||||
|
||||
fn validate<V: ValidateUnsigned<Call = Context::RuntimeCall>>(
|
||||
&self,
|
||||
source: TransactionSource,
|
||||
info: &DispatchInfo,
|
||||
_len: usize,
|
||||
) -> TransactionValidity {
|
||||
let mempool_priority_if_signed = match &self.0 {
|
||||
Transaction::Unsigned { .. } => {
|
||||
// Since this is the priority if signed, and this isn't signed, we return 0
|
||||
0
|
||||
}
|
||||
Transaction::Signed {
|
||||
calls: _,
|
||||
contextualized_signature:
|
||||
ContextualizedSignature { explicit_context: ExplicitContext { signer, fee, .. }, .. },
|
||||
} => {
|
||||
self.1.can_pay_fee(signer, *fee)?;
|
||||
|
||||
// Prioritize transactions by their fees
|
||||
// TODO: Re-evaluate this
|
||||
{
|
||||
let fee = fee.0;
|
||||
Weight::from_all(fee).checked_div_per_component(&info.call_weight).unwrap_or(0)
|
||||
}
|
||||
}
|
||||
};
|
||||
self.validate_except_fee::<V>(source, mempool_priority_if_signed)
|
||||
}
|
||||
|
||||
fn apply<V: ValidateUnsigned<Call = Context::RuntimeCall>>(
|
||||
self,
|
||||
_info: &DispatchInfo,
|
||||
_len: usize,
|
||||
) -> sp_runtime::ApplyExtrinsicResultWithInfo<PostDispatchInfo> {
|
||||
// We use 0 for the mempool priority, as this is no longer in the mempool so it's irrelevant
|
||||
self.validate_except_fee::<V>(TransactionSource::InBlock, 0)?;
|
||||
|
||||
// Start the transaction
|
||||
self.1.start_transaction();
|
||||
|
||||
let transaction_hash = self.0.hash();
|
||||
|
||||
let res = match self.0 {
|
||||
Transaction::Unsigned { call } => {
|
||||
let call = Context::RuntimeCall::from(call.0);
|
||||
V::pre_dispatch(&call)?;
|
||||
match call.dispatch(None.into()) {
|
||||
Ok(res) => Ok(Ok(res)),
|
||||
// Unsigned transactions should only be included if valid in all regards
|
||||
Err(_err) => Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(0))),
|
||||
}
|
||||
}
|
||||
Transaction::Signed {
|
||||
calls,
|
||||
contextualized_signature:
|
||||
ContextualizedSignature { explicit_context: ExplicitContext { signer, fee, .. }, .. },
|
||||
} => {
|
||||
// Consume the signer's next nonce
|
||||
self.1.consume_next_nonce(&signer);
|
||||
// Pay the fee
|
||||
self.1.pay_fee(&signer, fee)?;
|
||||
|
||||
let _res = frame_support::storage::transactional::with_storage_layer(|| {
|
||||
for call in calls.0 {
|
||||
let call = Context::RuntimeCall::from(call);
|
||||
match call.dispatch(Some(signer).into()) {
|
||||
Ok(_res) => {}
|
||||
// Because this call errored, don't continue and revert all prior calls
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// We don't care if the individual calls succeeded or failed.
|
||||
// The transaction was valid for inclusion and the fee was paid.
|
||||
// Either the calls passed, as desired, or they failed and the storage was reverted.
|
||||
Ok(Ok(PostDispatchInfo {
|
||||
// `None` stands for the worst case, which is what we want
|
||||
actual_weight: None,
|
||||
// Signed transactions always pay their fee
|
||||
// TODO: Do we want to handle this so we can not charge fees on removing genesis
|
||||
// liquidity?
|
||||
pays_fee: Pays::Yes,
|
||||
}))
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: TransactionSuccess/TransactionFailure event?
|
||||
|
||||
// End the transaction
|
||||
self.1.end_transaction(transaction_hash);
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "substrate")]
|
||||
pub use substrate::*;
|
||||
@@ -1,186 +0,0 @@
|
||||
use scale::Encode;
|
||||
|
||||
use sp_core::sr25519::{Public, Signature};
|
||||
use sp_runtime::traits::Verify;
|
||||
|
||||
use serai_primitives::SeraiAddress;
|
||||
|
||||
use frame_support::dispatch::GetDispatchInfo;
|
||||
|
||||
pub trait TransactionMember:
|
||||
Clone + PartialEq + Eq + core::fmt::Debug + scale::Encode + scale::Decode + scale_info::TypeInfo
|
||||
{
|
||||
}
|
||||
impl<
|
||||
T: Clone
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ core::fmt::Debug
|
||||
+ scale::Encode
|
||||
+ scale::Decode
|
||||
+ scale_info::TypeInfo,
|
||||
> TransactionMember for T
|
||||
{
|
||||
}
|
||||
|
||||
type TransactionEncodeAs<'a, Extra> =
|
||||
(&'a crate::Call, &'a Option<(SeraiAddress, Signature, Extra)>);
|
||||
type TransactionDecodeAs<Extra> = (crate::Call, Option<(SeraiAddress, Signature, Extra)>);
|
||||
|
||||
// We use our own Transaction struct, over UncheckedExtrinsic, for more control, a bit more
|
||||
// simplicity, and in order to be immune to https://github.com/paritytech/polkadot-sdk/issues/2947
|
||||
#[allow(private_bounds)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Transaction<
|
||||
Call: 'static + TransactionMember + From<crate::Call>,
|
||||
Extra: 'static + TransactionMember,
|
||||
> {
|
||||
call: crate::Call,
|
||||
mapped_call: Call,
|
||||
signature: Option<(SeraiAddress, Signature, Extra)>,
|
||||
}
|
||||
|
||||
impl<Call: 'static + TransactionMember + From<crate::Call>, Extra: 'static + TransactionMember>
|
||||
Transaction<Call, Extra>
|
||||
{
|
||||
pub fn new(call: crate::Call, signature: Option<(SeraiAddress, Signature, Extra)>) -> Self {
|
||||
Self { call: call.clone(), mapped_call: call.into(), signature }
|
||||
}
|
||||
|
||||
pub fn call(&self) -> &crate::Call {
|
||||
&self.call
|
||||
}
|
||||
}
|
||||
|
||||
impl<Call: 'static + TransactionMember + From<crate::Call>, Extra: 'static + TransactionMember>
|
||||
scale::Encode for Transaction<Call, Extra>
|
||||
{
|
||||
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
|
||||
let tx: TransactionEncodeAs<Extra> = (&self.call, &self.signature);
|
||||
tx.using_encoded(f)
|
||||
}
|
||||
}
|
||||
impl<Call: 'static + TransactionMember + From<crate::Call>, Extra: 'static + TransactionMember>
|
||||
scale::Decode for Transaction<Call, Extra>
|
||||
{
|
||||
fn decode<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
|
||||
let (call, signature) = TransactionDecodeAs::decode(input)?;
|
||||
let mapped_call = Call::from(call.clone());
|
||||
Ok(Self { call, mapped_call, signature })
|
||||
}
|
||||
}
|
||||
impl<Call: 'static + TransactionMember + From<crate::Call>, Extra: 'static + TransactionMember>
|
||||
scale_info::TypeInfo for Transaction<Call, Extra>
|
||||
{
|
||||
type Identity = TransactionDecodeAs<Extra>;
|
||||
|
||||
// Define the type info as the info of the type equivalent to what we encode as
|
||||
fn type_info() -> scale_info::Type {
|
||||
TransactionDecodeAs::<Extra>::type_info()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
mod _serde {
|
||||
use scale::Encode;
|
||||
use serde::ser::*;
|
||||
use super::*;
|
||||
impl<Call: 'static + TransactionMember + From<crate::Call>, Extra: 'static + TransactionMember>
|
||||
Serialize for Transaction<Call, Extra>
|
||||
{
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let encoded = self.encode();
|
||||
serializer.serialize_bytes(&encoded)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use serde::de::*;
|
||||
#[cfg(feature = "std")]
|
||||
impl<
|
||||
'a,
|
||||
Call: 'static + TransactionMember + From<crate::Call>,
|
||||
Extra: 'static + TransactionMember,
|
||||
> Deserialize<'a> for Transaction<Call, Extra>
|
||||
{
|
||||
fn deserialize<D: Deserializer<'a>>(de: D) -> Result<Self, D::Error> {
|
||||
let bytes = sp_core::bytes::deserialize(de)?;
|
||||
<Self as scale::Decode>::decode(&mut &bytes[..])
|
||||
.map_err(|e| serde::de::Error::custom(format!("invalid transaction: {e}")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Call: 'static + TransactionMember + From<crate::Call> + TryInto<crate::Call>,
|
||||
Extra: 'static + TransactionMember,
|
||||
> sp_runtime::traits::Extrinsic for Transaction<Call, Extra>
|
||||
{
|
||||
type Call = Call;
|
||||
type SignaturePayload = (SeraiAddress, Signature, Extra);
|
||||
fn is_signed(&self) -> Option<bool> {
|
||||
Some(self.signature.is_some())
|
||||
}
|
||||
fn new(call: Call, signature: Option<Self::SignaturePayload>) -> Option<Self> {
|
||||
Some(Self { call: call.clone().try_into().ok()?, mapped_call: call, signature })
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Call: 'static + TransactionMember + From<crate::Call> + TryInto<crate::Call>,
|
||||
Extra: 'static + TransactionMember,
|
||||
> frame_support::traits::ExtrinsicCall for Transaction<Call, Extra>
|
||||
{
|
||||
fn call(&self) -> &Call {
|
||||
&self.mapped_call
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Call: 'static + TransactionMember + From<crate::Call>,
|
||||
Extra: 'static + TransactionMember + sp_runtime::traits::SignedExtension,
|
||||
> sp_runtime::traits::ExtrinsicMetadata for Transaction<Call, Extra>
|
||||
{
|
||||
type SignedExtensions = Extra;
|
||||
|
||||
const VERSION: u8 = 0;
|
||||
}
|
||||
|
||||
impl<
|
||||
Call: 'static + TransactionMember + From<crate::Call> + GetDispatchInfo,
|
||||
Extra: 'static + TransactionMember,
|
||||
> GetDispatchInfo for Transaction<Call, Extra>
|
||||
{
|
||||
fn get_dispatch_info(&self) -> frame_support::dispatch::DispatchInfo {
|
||||
self.mapped_call.get_dispatch_info()
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Call: 'static + TransactionMember + From<crate::Call>,
|
||||
Extra: 'static + TransactionMember + sp_runtime::traits::SignedExtension,
|
||||
> sp_runtime::traits::BlindCheckable for Transaction<Call, Extra>
|
||||
{
|
||||
type Checked = sp_runtime::generic::CheckedExtrinsic<Public, Call, Extra>;
|
||||
|
||||
fn check(
|
||||
self,
|
||||
) -> Result<Self::Checked, sp_runtime::transaction_validity::TransactionValidityError> {
|
||||
Ok(match self.signature {
|
||||
Some((signer, signature, extra)) => {
|
||||
if !signature.verify(
|
||||
(&self.call, &extra, extra.additional_signed()?).encode().as_slice(),
|
||||
&signer.into(),
|
||||
) {
|
||||
Err(sp_runtime::transaction_validity::InvalidTransaction::BadProof)?
|
||||
}
|
||||
|
||||
sp_runtime::generic::CheckedExtrinsic {
|
||||
signed: Some((signer.into(), extra)),
|
||||
function: self.mapped_call,
|
||||
}
|
||||
}
|
||||
None => sp_runtime::generic::CheckedExtrinsic { signed: None, function: self.mapped_call },
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,79 +1,145 @@
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
use sp_core::{ConstU32, bounded::BoundedVec};
|
||||
|
||||
pub use serai_validator_sets_primitives as primitives;
|
||||
use serai_primitives::{
|
||||
crypto::{ExternalKey, KeyPair, Signature},
|
||||
address::SeraiAddress,
|
||||
balance::Amount,
|
||||
network_id::*,
|
||||
validator_sets::*,
|
||||
};
|
||||
|
||||
use serai_primitives::*;
|
||||
use serai_validator_sets_primitives::*;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
/// A call to the validator sets.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Call {
|
||||
/// Set the keys for a validator set.
|
||||
set_keys {
|
||||
network: ExternalNetworkId,
|
||||
/// The validator set which is setting their keys.
|
||||
validator_set: ExternalValidatorSet,
|
||||
/// The keys being set.
|
||||
key_pair: KeyPair,
|
||||
/// The participants in the validator set who signed off on these keys.
|
||||
// TODO: Bound
|
||||
#[borsh(
|
||||
serialize_with = "serai_primitives::sp_borsh::borsh_serialize_bitvec",
|
||||
deserialize_with = "serai_primitives::sp_borsh::borsh_deserialize_bitvec"
|
||||
)]
|
||||
signature_participants: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>,
|
||||
/// The signature confirming these keys are valid.
|
||||
signature: Signature,
|
||||
},
|
||||
set_embedded_elliptic_curve_key {
|
||||
embedded_elliptic_curve: EmbeddedEllipticCurve,
|
||||
key: BoundedVec<u8, ConstU32<{ MAX_KEY_LEN }>>,
|
||||
},
|
||||
/// Report a validator set's slashes onto Serai.
|
||||
report_slashes {
|
||||
network: ExternalNetworkId,
|
||||
/// The validator set which is setting their keys.
|
||||
validator_set: ExternalValidatorSet,
|
||||
/// The slashes they're reporting.
|
||||
slashes: SlashReport,
|
||||
/// The signature confirming the validity of this slash report.
|
||||
signature: Signature,
|
||||
},
|
||||
/// Set a validator's keys on embedded elliptic curves for a specific network.
|
||||
set_embedded_elliptic_curve_keys {
|
||||
/// The network the origin is setting their embedded elliptic curve keys for.
|
||||
network: ExternalNetworkId,
|
||||
/// The keys on the embedded elliptic curves.
|
||||
///
|
||||
/// This may be a single key if the external network uses the same embedded elliptic curve as
|
||||
/// used for the key to oraclize onto Serai.
|
||||
#[borsh(
|
||||
serialize_with = "serai_primitives::sp_borsh::borsh_serialize_bounded_vec",
|
||||
deserialize_with = "serai_primitives::sp_borsh::borsh_deserialize_bounded_vec"
|
||||
)]
|
||||
keys: BoundedVec<u8, ConstU32<{ 2 * ExternalKey::MAX_LEN }>>,
|
||||
},
|
||||
/// Allocate stake to a network.
|
||||
allocate {
|
||||
/// The network to allocate stake to.
|
||||
network: NetworkId,
|
||||
/// The amount of stake to allocate.
|
||||
amount: Amount,
|
||||
},
|
||||
/// Deallocate stake from a network.
|
||||
///
|
||||
/// This deallocation may be immediate or may be delayed depending on if the origin is an
|
||||
/// active, or even recent, validator. If delayed, it will have to be claimed at a later time.
|
||||
deallocate {
|
||||
/// The network to deallocate stake from.
|
||||
network: NetworkId,
|
||||
/// The amount of stake to deallocate.
|
||||
amount: Amount,
|
||||
},
|
||||
/// Claim a now-unlocked deallocation.
|
||||
claim_deallocation {
|
||||
network: NetworkId,
|
||||
session: Session,
|
||||
/// The validator set which claiming the deallocation was delayed until.
|
||||
deallocation: ValidatorSet,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||
impl Call {
|
||||
pub(crate) fn is_signed(&self) -> bool {
|
||||
match self {
|
||||
Call::set_keys { .. } | Call::report_slashes { .. } => false,
|
||||
Call::set_embedded_elliptic_curve_keys { .. } |
|
||||
Call::allocate { .. } |
|
||||
Call::deallocate { .. } |
|
||||
Call::claim_deallocation { .. } => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An event from the validator sets.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Event {
|
||||
/// A new validator set was declared.
|
||||
NewSet {
|
||||
/// The set declared.
|
||||
set: ValidatorSet,
|
||||
},
|
||||
ParticipantRemoved {
|
||||
set: ValidatorSet,
|
||||
removed: SeraiAddress,
|
||||
},
|
||||
KeyGen {
|
||||
/// A validator set has set their keys.
|
||||
SetKeys {
|
||||
/// The set which set their keys.
|
||||
set: ExternalValidatorSet,
|
||||
/// The keys sets.
|
||||
key_pair: KeyPair,
|
||||
},
|
||||
/// A validator set has accepted responsibility from the prior validator set.
|
||||
AcceptedHandover {
|
||||
/// The set which accepted responsibility from the prior set.
|
||||
set: ValidatorSet,
|
||||
},
|
||||
/// A validator set has retired.
|
||||
SetRetired {
|
||||
/// The set retired.
|
||||
set: ValidatorSet,
|
||||
},
|
||||
AllocationIncreased {
|
||||
/// A validator's allocation to a network has increased.
|
||||
Allocation {
|
||||
/// The validator who increased their allocation.
|
||||
validator: SeraiAddress,
|
||||
/// The network the stake was allocated to.
|
||||
network: NetworkId,
|
||||
/// The amount of stake allocated.
|
||||
amount: Amount,
|
||||
},
|
||||
AllocationDecreased {
|
||||
/// A validator's allocation to a network has decreased.
|
||||
Deallocation {
|
||||
/// The validator who decreased their allocation.
|
||||
validator: SeraiAddress,
|
||||
/// The network the stake was deallocated from.
|
||||
network: NetworkId,
|
||||
/// The amount of stake deallocated.
|
||||
amount: Amount,
|
||||
/// The session which claiming the deallocation was delayed until.
|
||||
delayed_until: Option<Session>,
|
||||
},
|
||||
/// A validator's deallocation from a network has been claimed.
|
||||
///
|
||||
/// This is only emited for deallocations which were delayed and has to be explicitly claimed.
|
||||
DeallocationClaimed {
|
||||
/// The validator who claimed their deallocation.
|
||||
validator: SeraiAddress,
|
||||
network: NetworkId,
|
||||
session: Session,
|
||||
/// The validator set the deallocation was delayed until.
|
||||
deallocation: ValidatorSet,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ serde_json = { version = "1", optional = true }
|
||||
serai-abi = { path = "../abi", version = "0.1" }
|
||||
|
||||
multiaddr = { version = "0.18", optional = true }
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate", optional = true }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", optional = true }
|
||||
frame-system = { git = "https://github.com/serai-dex/substrate", optional = true }
|
||||
sp-core = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", optional = true }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", optional = true }
|
||||
frame-system = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", optional = true }
|
||||
|
||||
async-lock = "3"
|
||||
|
||||
@@ -60,8 +60,7 @@ dockertest = "0.5"
|
||||
serai-docker-tests = { path = "../../tests/docker" }
|
||||
|
||||
[features]
|
||||
serai = ["thiserror/std", "serde", "serde_json", "serai-abi/serde", "multiaddr", "sp-core", "sp-runtime", "frame-system", "simple-request"]
|
||||
borsh = ["serai-abi/borsh"]
|
||||
serai = ["thiserror/std", "serde", "serde_json", "multiaddr", "sp-core", "sp-runtime", "frame-system", "simple-request"]
|
||||
|
||||
networks = []
|
||||
bitcoin = ["networks", "dep:bitcoin"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use sp_core::bounded_vec::BoundedVec;
|
||||
use sp_core::bounded::BoundedVec;
|
||||
use serai_abi::primitives::{Amount, Coin, ExternalCoin, SeraiAddress};
|
||||
|
||||
use crate::{SeraiError, TemporalSerai};
|
||||
|
||||
@@ -6,7 +6,7 @@ use rand_core::OsRng;
|
||||
|
||||
use sp_core::{
|
||||
ConstU32,
|
||||
bounded_vec::BoundedVec,
|
||||
bounded::BoundedVec,
|
||||
sr25519::{Pair, Signature},
|
||||
Pair as PairTrait,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use rand_core::{RngCore, OsRng};
|
||||
|
||||
use sp_core::{Pair as PairTrait, bounded_vec::BoundedVec};
|
||||
use sp_core::{Pair as PairTrait, bounded::BoundedVec};
|
||||
|
||||
use serai_abi::in_instructions::primitives::DexCall;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "serai-coins-pallet"
|
||||
version = "0.1.0"
|
||||
description = "Coins pallet for Serai"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/coins/pallet"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/coins"
|
||||
authors = ["Akil Demir <akildemir72@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.80"
|
||||
@@ -22,41 +22,33 @@ workspace = true
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-core = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-std = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
frame-system = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
|
||||
pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../../primitives", default-features = false, features = ["serde"] }
|
||||
coins-primitives = { package = "serai-coins-primitives", path = "../primitives", default-features = false }
|
||||
serai-primitives = { path = "../primitives", default-features = false, features = ["serde", "non_canonical_scale_derivations"] }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-io = { git = "https://github.com/serai-dex/substrate", default-features = false, features = ["std"] }
|
||||
sp-io = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false, features = ["std"] }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"frame-system/std",
|
||||
"frame-support/std",
|
||||
|
||||
"sp-core/std",
|
||||
"sp-std/std",
|
||||
"sp-runtime/std",
|
||||
|
||||
"pallet-transaction-payment/std",
|
||||
"frame-system/std",
|
||||
"frame-support/std",
|
||||
|
||||
"serai-primitives/std",
|
||||
"coins-primitives/std",
|
||||
]
|
||||
|
||||
try-runtime = [
|
||||
"frame-system/try-runtime",
|
||||
"frame-support/try-runtime",
|
||||
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
|
||||
runtime-benchmarks = [
|
||||
3
substrate/coins/README.md
Normal file
3
substrate/coins/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Coins Pallet
|
||||
|
||||
Pallet implementing the necessary coins logic for the Serai protocol.
|
||||
@@ -1,303 +0,0 @@
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use serai_primitives::{Balance, Coin, ExternalBalance, SubstrateAmount};
|
||||
|
||||
pub trait AllowMint {
|
||||
fn is_allowed(balance: &ExternalBalance) -> bool;
|
||||
}
|
||||
|
||||
impl AllowMint for () {
|
||||
fn is_allowed(_: &ExternalBalance) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Investigate why Substrate generates this
|
||||
#[allow(unreachable_patterns, clippy::cast_possible_truncation)]
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use sp_std::{vec::Vec, any::TypeId};
|
||||
use sp_core::sr25519::Public;
|
||||
use sp_runtime::{
|
||||
traits::{DispatchInfoOf, PostDispatchInfoOf},
|
||||
transaction_validity::{TransactionValidityError, InvalidTransaction},
|
||||
};
|
||||
|
||||
use frame_system::pallet_prelude::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
|
||||
use pallet_transaction_payment::{Config as TpConfig, OnChargeTransaction};
|
||||
|
||||
use serai_primitives::*;
|
||||
pub use coins_primitives as primitives;
|
||||
use primitives::*;
|
||||
|
||||
type LiquidityTokensInstance = crate::Instance1;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config<I: 'static = ()>: frame_system::Config<AccountId = Public> {
|
||||
type RuntimeEvent: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
type AllowMint: AllowMint;
|
||||
}
|
||||
|
||||
#[pallet::genesis_config]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
|
||||
pub accounts: Vec<(T::AccountId, Balance)>,
|
||||
pub _ignore: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> {
|
||||
fn default() -> Self {
|
||||
GenesisConfig { accounts: Default::default(), _ignore: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T, I = ()> {
|
||||
AmountOverflowed,
|
||||
NotEnoughCoins,
|
||||
BurnWithInstructionNotAllowed,
|
||||
MintNotAllowed,
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(fn deposit_event)]
|
||||
pub enum Event<T: Config<I>, I: 'static = ()> {
|
||||
Mint { to: Public, balance: Balance },
|
||||
Burn { from: Public, balance: Balance },
|
||||
BurnWithInstruction { from: Public, instruction: OutInstructionWithBalance },
|
||||
Transfer { from: Public, to: Public, balance: Balance },
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T, I = ()>(_);
|
||||
|
||||
/// The amount of coins each account has.
|
||||
// Identity is used as the second key's hasher due to it being a non-manipulatable fixed-space
|
||||
// ID.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn balances)]
|
||||
pub type Balances<T: Config<I>, I: 'static = ()> =
|
||||
StorageDoubleMap<_, Blake2_128Concat, Public, Identity, Coin, SubstrateAmount, ValueQuery>;
|
||||
|
||||
/// The total supply of each coin.
|
||||
// We use Identity type here again due to reasons stated in the Balances Storage.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn supply)]
|
||||
pub type Supply<T: Config<I>, I: 'static = ()> =
|
||||
StorageMap<_, Identity, Coin, SubstrateAmount, ValueQuery>;
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
|
||||
fn build(&self) {
|
||||
// initialize the supply of the coins
|
||||
// TODO: Don't use COINS yet GenesisConfig so we can safely expand COINS
|
||||
for c in &COINS {
|
||||
Supply::<T, I>::set(c, 0);
|
||||
}
|
||||
|
||||
// initialize the genesis accounts
|
||||
for (account, balance) in &self.accounts {
|
||||
Pallet::<T, I>::mint(*account, *balance).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
|
||||
fn on_initialize(_: BlockNumberFor<T>) -> Weight {
|
||||
// burn the fees collected previous block
|
||||
let coin = Coin::Serai;
|
||||
let amount = Self::balance(FEE_ACCOUNT.into(), coin);
|
||||
// we can unwrap, we are not burning more then what we have
|
||||
// If this errors, it'll halt the runtime however (due to being called at the start of every
|
||||
// block), requiring extra care when reviewing
|
||||
Self::burn_internal(FEE_ACCOUNT.into(), Balance { coin, amount }).unwrap();
|
||||
Weight::zero() // TODO
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
/// Returns the balance of a given account for `coin`.
|
||||
pub fn balance(of: Public, coin: Coin) -> Amount {
|
||||
Amount(Self::balances(of, coin))
|
||||
}
|
||||
|
||||
fn decrease_balance_internal(from: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
||||
let coin = &balance.coin;
|
||||
|
||||
// sub amount from account
|
||||
let new_amount = Self::balances(from, coin)
|
||||
.checked_sub(balance.amount.0)
|
||||
.ok_or(Error::<T, I>::NotEnoughCoins)?;
|
||||
|
||||
// save
|
||||
if new_amount == 0 {
|
||||
Balances::<T, I>::remove(from, coin);
|
||||
} else {
|
||||
Balances::<T, I>::set(from, coin, new_amount);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn increase_balance_internal(to: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
||||
let coin = &balance.coin;
|
||||
|
||||
// add amount to account
|
||||
let new_amount = Self::balances(to, coin)
|
||||
.checked_add(balance.amount.0)
|
||||
.ok_or(Error::<T, I>::AmountOverflowed)?;
|
||||
|
||||
// save
|
||||
Balances::<T, I>::set(to, coin, new_amount);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mint `balance` to the given account.
|
||||
///
|
||||
/// Errors if any amount overflows.
|
||||
pub fn mint(to: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
||||
// If the coin isn't Serai, which we're always allowed to mint, and the mint isn't explicitly
|
||||
// allowed, error
|
||||
if !ExternalCoin::try_from(balance.coin)
|
||||
.map(|coin| T::AllowMint::is_allowed(&ExternalBalance { coin, amount: balance.amount }))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
Err(Error::<T, I>::MintNotAllowed)?;
|
||||
}
|
||||
|
||||
// update the balance
|
||||
Self::increase_balance_internal(to, balance)?;
|
||||
|
||||
// update the supply
|
||||
let new_supply = Self::supply(balance.coin)
|
||||
.checked_add(balance.amount.0)
|
||||
.ok_or(Error::<T, I>::AmountOverflowed)?;
|
||||
Supply::<T, I>::set(balance.coin, new_supply);
|
||||
|
||||
Self::deposit_event(Event::Mint { to, balance });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Burn `balance` from the specified account.
|
||||
fn burn_internal(from: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
||||
// don't waste time if amount == 0
|
||||
if balance.amount.0 == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// update the balance
|
||||
Self::decrease_balance_internal(from, balance)?;
|
||||
|
||||
// update the supply
|
||||
let new_supply = Self::supply(balance.coin).checked_sub(balance.amount.0).unwrap();
|
||||
Supply::<T, I>::set(balance.coin, new_supply);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transfer `balance` from `from` to `to`.
|
||||
pub fn transfer_internal(
|
||||
from: Public,
|
||||
to: Public,
|
||||
balance: Balance,
|
||||
) -> Result<(), Error<T, I>> {
|
||||
// update balances of accounts
|
||||
Self::decrease_balance_internal(from, balance)?;
|
||||
Self::increase_balance_internal(to, balance)?;
|
||||
Self::deposit_event(Event::Transfer { from, to, balance });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
||||
pub fn transfer(origin: OriginFor<T>, to: Public, balance: Balance) -> DispatchResult {
|
||||
let from = ensure_signed(origin)?;
|
||||
Self::transfer_internal(from, to, balance)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Burn `balance` from the caller.
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
||||
pub fn burn(origin: OriginFor<T>, balance: Balance) -> DispatchResult {
|
||||
let from = ensure_signed(origin)?;
|
||||
Self::burn_internal(from, balance)?;
|
||||
Self::deposit_event(Event::Burn { from, balance });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Burn `balance` with `OutInstructionWithBalance` from the caller.
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
||||
pub fn burn_with_instruction(
|
||||
origin: OriginFor<T>,
|
||||
instruction: OutInstructionWithBalance,
|
||||
) -> DispatchResult {
|
||||
if TypeId::of::<I>() == TypeId::of::<LiquidityTokensInstance>() {
|
||||
Err(Error::<T, I>::BurnWithInstructionNotAllowed)?;
|
||||
}
|
||||
|
||||
let from = ensure_signed(origin)?;
|
||||
Self::burn_internal(from, instruction.balance.into())?;
|
||||
Self::deposit_event(Event::BurnWithInstruction { from, instruction });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> OnChargeTransaction<T> for Pallet<T>
|
||||
where
|
||||
T: TpConfig,
|
||||
{
|
||||
type Balance = SubstrateAmount;
|
||||
type LiquidityInfo = Option<SubstrateAmount>;
|
||||
|
||||
fn withdraw_fee(
|
||||
who: &Public,
|
||||
_call: &T::RuntimeCall,
|
||||
_dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
|
||||
fee: Self::Balance,
|
||||
_tip: Self::Balance,
|
||||
) -> Result<Self::LiquidityInfo, TransactionValidityError> {
|
||||
if fee == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let balance = Balance { coin: Coin::Serai, amount: Amount(fee) };
|
||||
match Self::transfer_internal(*who, FEE_ACCOUNT.into(), balance) {
|
||||
Err(_) => Err(InvalidTransaction::Payment)?,
|
||||
Ok(()) => Ok(Some(fee)),
|
||||
}
|
||||
}
|
||||
|
||||
fn correct_and_deposit_fee(
|
||||
who: &Public,
|
||||
_dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
|
||||
_post_info: &PostDispatchInfoOf<T::RuntimeCall>,
|
||||
corrected_fee: Self::Balance,
|
||||
_tip: Self::Balance,
|
||||
already_withdrawn: Self::LiquidityInfo,
|
||||
) -> Result<(), TransactionValidityError> {
|
||||
if let Some(paid) = already_withdrawn {
|
||||
let refund_amount = paid.saturating_sub(corrected_fee);
|
||||
let balance = Balance { coin: Coin::Serai, amount: Amount(refund_amount) };
|
||||
Self::transfer_internal(FEE_ACCOUNT.into(), *who, balance)
|
||||
.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use pallet::*;
|
||||
@@ -1,70 +0,0 @@
|
||||
//! Test environment for Coins pallet.
|
||||
|
||||
use super::*;
|
||||
|
||||
use frame_support::{
|
||||
construct_runtime,
|
||||
traits::{ConstU32, ConstU64},
|
||||
};
|
||||
|
||||
use sp_core::{H256, sr25519::Public};
|
||||
use sp_runtime::{
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
BuildStorage,
|
||||
};
|
||||
|
||||
use crate as coins;
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: frame_system,
|
||||
Coins: coins,
|
||||
}
|
||||
);
|
||||
|
||||
impl frame_system::Config for Test {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Nonce = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = Public;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ConstU64<250>;
|
||||
type DbWeight = ();
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = ConstU32<16>;
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
|
||||
type AllowMint = ();
|
||||
}
|
||||
|
||||
pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
|
||||
crate::GenesisConfig::<Test> { accounts: vec![], _ignore: Default::default() }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = sp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(0));
|
||||
ext
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
[package]
|
||||
name = "serai-coins-primitives"
|
||||
version = "0.1.0"
|
||||
description = "Serai coins primitives"
|
||||
license = "MIT"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.80"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
zeroize = { version = "^1.5", features = ["derive"], optional = true }
|
||||
|
||||
borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"], optional = true }
|
||||
serde = { version = "1", default-features = false, features = ["derive", "alloc"], optional = true }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
serai-primitives = { path = "../../primitives", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
[features]
|
||||
std = ["zeroize", "borsh?/std", "serde?/std", "scale/std", "scale-info/std", "sp-runtime/std", "serai-primitives/std"]
|
||||
borsh = ["dep:borsh", "serai-primitives/borsh"]
|
||||
serde = ["dep:serde", "serai-primitives/serde"]
|
||||
default = ["std"]
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Luke Parker
|
||||
|
||||
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.
|
||||
@@ -1,54 +0,0 @@
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[cfg(feature = "borsh")]
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use scale::{Encode, Decode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
use serai_primitives::{ExternalBalance, SeraiAddress, ExternalAddress, system_address};
|
||||
|
||||
pub const FEE_ACCOUNT: SeraiAddress = system_address(b"Coins-fees");
|
||||
|
||||
// TODO: Replace entirely with just Address
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct OutInstruction {
|
||||
pub address: ExternalAddress,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct OutInstructionWithBalance {
|
||||
pub instruction: OutInstruction,
|
||||
pub balance: ExternalBalance,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Destination {
|
||||
Native(SeraiAddress),
|
||||
External(OutInstruction),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address() {
|
||||
use sp_runtime::traits::TrailingZeroInput;
|
||||
assert_eq!(
|
||||
FEE_ACCOUNT,
|
||||
SeraiAddress::decode(&mut TrailingZeroInput::new(b"Coins-fees")).unwrap()
|
||||
);
|
||||
}
|
||||
294
substrate/coins/src/lib.rs
Normal file
294
substrate/coins/src/lib.rs
Normal file
@@ -0,0 +1,294 @@
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use serai_primitives::balance::ExternalBalance;
|
||||
|
||||
/// The decider for if a mint is allowed or not.
|
||||
pub trait AllowMint {
|
||||
/// Whether or not the mint of the specified coins is allowed.
|
||||
fn is_allowed(balance: &ExternalBalance) -> bool;
|
||||
}
|
||||
|
||||
/// An `AllowMint` implementor which always returns true.
|
||||
pub struct AlwaysAllowMint;
|
||||
impl AllowMint for AlwaysAllowMint {
|
||||
fn is_allowed(_: &ExternalBalance) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
#[frame_support::pallet]
|
||||
mod pallet {
|
||||
use core::any::TypeId;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use sp_core::sr25519::Public;
|
||||
|
||||
use frame_system::pallet_prelude::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
|
||||
use serai_primitives::{coin::*, balance::*, instructions::OutInstructionWithBalance};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// The instance used to represent coins on the Serai network.
|
||||
///
|
||||
/// This would either be SRI itself or the sriXYZ coins swappable via pools.
|
||||
pub struct CoinsInstance;
|
||||
/// The instance used to represent liquidity tokens on the Serai network.
|
||||
///
|
||||
/// Coin::XYZ would be considered as the liquidity token for the Coin::SRI - Coin::XYZ pool.
|
||||
pub struct LiquidityTokensInstance;
|
||||
|
||||
/// The configuration of this pallet.
|
||||
#[pallet::config]
|
||||
pub trait Config<I: 'static = ()>: frame_system::Config<AccountId = Public> {
|
||||
/// The event type.
|
||||
type RuntimeEvent: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
/// What decides if mints are allowed.
|
||||
type AllowMint: AllowMint;
|
||||
}
|
||||
|
||||
/// The genesis state to use for this pallet.
|
||||
#[pallet::genesis_config]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
|
||||
/// The balances to initiate the state with.
|
||||
///
|
||||
/// This is useful for test networks where it's desirable to have some coins available
|
||||
/// immediately.
|
||||
pub accounts: Vec<(T::AccountId, Balance)>,
|
||||
/// PhantomData to bind `I`.
|
||||
pub _instance: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> {
|
||||
fn default() -> Self {
|
||||
GenesisConfig { accounts: Default::default(), _instance: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
/// An error incurred.
|
||||
#[pallet::error]
|
||||
pub enum Error<T, I = ()> {
|
||||
/// The mint wasn't allowed.
|
||||
MintNotAllowed,
|
||||
/// The amount an account had overflowed.
|
||||
AmountOverflowed,
|
||||
/// The account didn't have enough coins for this operation.
|
||||
NotEnoughCoins,
|
||||
/// An Instruction was specified with a Burn when that's unsupported.
|
||||
BurnWithInstructionNotAllowed,
|
||||
}
|
||||
|
||||
/// An event emitted.
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(fn deposit_event)]
|
||||
pub enum Event<T: Config<I>, I: 'static = ()> {
|
||||
/// Coins were minted.
|
||||
Mint {
|
||||
/// The account minted to.
|
||||
to: Public,
|
||||
/// The balance minted.
|
||||
balance: Balance,
|
||||
},
|
||||
/// Coins were transferred.
|
||||
Transfer {
|
||||
/// The account transferred from.
|
||||
from: Public,
|
||||
/// The account transferred to.
|
||||
to: Public,
|
||||
/// The balance transferred.
|
||||
balance: Balance,
|
||||
},
|
||||
/// Coins were burnt.
|
||||
Burn {
|
||||
/// The account burnt from.
|
||||
from: Public,
|
||||
/// The balance burnt.
|
||||
balance: Balance,
|
||||
},
|
||||
/// Coins were burnt with an instruction.
|
||||
BurnWithInstruction {
|
||||
/// The account burnt from.
|
||||
from: Public,
|
||||
/// The instruction, and associated balance.
|
||||
instruction: OutInstructionWithBalance,
|
||||
},
|
||||
}
|
||||
|
||||
/// The Pallet struct.
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T, I = ()>(_);
|
||||
|
||||
/// The amount of coins each account has.
|
||||
// Identity is used as the second key's hasher due to Coin being a small, fixed-space ID.
|
||||
#[pallet::storage]
|
||||
type Balances<T: Config<I>, I: 'static = ()> =
|
||||
StorageDoubleMap<_, Blake2_128Concat, Public, Identity, Coin, Amount, ValueQuery>;
|
||||
|
||||
/// The total supply of each coin.
|
||||
#[pallet::storage]
|
||||
type Supply<T: Config<I>, I: 'static = ()> = StorageMap<_, Identity, Coin, Amount, ValueQuery>;
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
|
||||
fn build(&self) {
|
||||
// initialize the genesis accounts
|
||||
for (account, balance) in &self.accounts {
|
||||
Pallet::<T, I>::mint(*account, *balance).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
/// Returns the balance of `coin` for the specified account.
|
||||
pub fn balance(
|
||||
of: impl scale::EncodeLike<Public>,
|
||||
coin: impl scale::EncodeLike<Coin>,
|
||||
) -> Amount {
|
||||
Balances::<T, I>::get(of, coin)
|
||||
}
|
||||
|
||||
/// Returns the supply of `coin`.
|
||||
pub fn supply(coin: impl scale::EncodeLike<Coin>) -> Amount {
|
||||
Supply::<T, I>::get(coin)
|
||||
}
|
||||
|
||||
fn decrease_balance_internal(from: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
||||
let coin = &balance.coin;
|
||||
|
||||
// sub amount from account
|
||||
let new_amount =
|
||||
(Self::balance(from, coin) - balance.amount).ok_or(Error::<T, I>::NotEnoughCoins)?;
|
||||
|
||||
// save
|
||||
if new_amount == Amount(0) {
|
||||
Balances::<T, I>::remove(from, coin);
|
||||
} else {
|
||||
Balances::<T, I>::set(from, coin, new_amount);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn increase_balance_internal(to: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
||||
let coin = &balance.coin;
|
||||
|
||||
// add amount to account
|
||||
let new_amount =
|
||||
(Self::balance(to, coin) + balance.amount).ok_or(Error::<T, I>::AmountOverflowed)?;
|
||||
|
||||
// save
|
||||
Balances::<T, I>::set(to, coin, new_amount);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Mint `balance` to the given account.
|
||||
///
|
||||
/// Errors if any amount overflows.
|
||||
pub fn mint(to: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
||||
{
|
||||
// If this is an external coin, check if we can mint it
|
||||
let external_balance = ExternalBalance::try_from(balance);
|
||||
let can_mint_external = external_balance.as_ref().map(T::AllowMint::is_allowed);
|
||||
// If it was native to the Serai network, we can always mint it
|
||||
let can_mint = can_mint_external.unwrap_or(true);
|
||||
if !can_mint {
|
||||
Err(Error::<T, I>::MintNotAllowed)?;
|
||||
}
|
||||
}
|
||||
|
||||
// update the balance
|
||||
Self::increase_balance_internal(to, balance)?;
|
||||
|
||||
// update the supply
|
||||
let new_supply = (Supply::<T, I>::get(balance.coin) + balance.amount)
|
||||
.ok_or(Error::<T, I>::AmountOverflowed)?;
|
||||
Supply::<T, I>::set(balance.coin, new_supply);
|
||||
|
||||
Self::deposit_event(Event::Mint { to, balance });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Burn `balance` from the specified account.
|
||||
fn burn_internal(from: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
||||
// update the balance
|
||||
Self::decrease_balance_internal(from, balance)?;
|
||||
|
||||
// update the supply
|
||||
Supply::<T, I>::mutate(balance.coin, |supply| {
|
||||
// We can unwrap here as we're burning an amount legitimately in the system (per successful
|
||||
// decrease), so the supply must be greater than this value
|
||||
let new_supply = (*supply - balance.amount).unwrap();
|
||||
*supply = new_supply;
|
||||
});
|
||||
|
||||
// We don't emit the event here, but rather at the call-site, due to being unsure if we
|
||||
// should emit `Burn` or `BurnWithInstruction`
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transfer `balance` from `from` to `to`.
|
||||
fn transfer_internal(from: Public, to: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
||||
// update balances of accounts
|
||||
Self::decrease_balance_internal(from, balance)?;
|
||||
Self::increase_balance_internal(to, balance)?;
|
||||
Self::deposit_event(Event::Transfer { from, to, balance });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
/// Transfer `balance` from the signer to `to`.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
||||
pub fn transfer(origin: OriginFor<T>, to: Public, balance: Balance) -> DispatchResult {
|
||||
let from = ensure_signed(origin)?;
|
||||
Self::transfer_internal(from, to, balance)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Burn `balance` from the signer.
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
||||
pub fn burn(origin: OriginFor<T>, balance: Balance) -> DispatchResult {
|
||||
let from = ensure_signed(origin)?;
|
||||
Self::burn_internal(from, balance)?;
|
||||
Self::deposit_event(Event::Burn { from, balance });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Burn `balance` from the signer with `instruction`.
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
||||
pub fn burn_with_instruction(
|
||||
origin: OriginFor<T>,
|
||||
instruction: OutInstructionWithBalance,
|
||||
) -> DispatchResult {
|
||||
// Only allow specifying an instruction if this is Coins, not LiquidityTokens
|
||||
if TypeId::of::<I>() != TypeId::of::<CoinsInstance>() {
|
||||
Err(Error::<T, I>::BurnWithInstructionNotAllowed)?;
|
||||
}
|
||||
|
||||
let from = ensure_signed(origin)?;
|
||||
Self::burn_internal(from, instruction.balance.into())?;
|
||||
Self::deposit_event(Event::BurnWithInstruction { from, instruction });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use pallet::*;
|
||||
37
substrate/coins/src/mock.rs
Normal file
37
substrate/coins/src/mock.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
//! Test environment for Coins pallet.
|
||||
|
||||
use sp_runtime::BuildStorage;
|
||||
|
||||
use frame_support::{derive_impl, construct_runtime};
|
||||
|
||||
use crate::{self as coins, CoinsInstance};
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: frame_system,
|
||||
Coins: coins::<CoinsInstance>,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
||||
impl frame_system::Config for Test {
|
||||
type AccountId = sp_core::sr25519::Public;
|
||||
type Lookup = sp_runtime::traits::IdentityLookup<Self::AccountId>;
|
||||
type Block = frame_system::mocking::MockBlock<Test>;
|
||||
}
|
||||
|
||||
impl crate::Config<CoinsInstance> for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type AllowMint = crate::AlwaysAllowMint;
|
||||
}
|
||||
|
||||
pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
|
||||
let mut storage = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
|
||||
crate::GenesisConfig::<Test, CoinsInstance> { accounts: vec![], _instance: Default::default() }
|
||||
.assimilate_storage(&mut storage)
|
||||
.unwrap();
|
||||
|
||||
storage.into()
|
||||
}
|
||||
@@ -1,18 +1,18 @@
|
||||
use crate::{mock::*, primitives::*};
|
||||
|
||||
use sp_core::{Pair as _, sr25519::Pair};
|
||||
use frame_system::RawOrigin;
|
||||
use sp_core::Pair;
|
||||
|
||||
use serai_primitives::*;
|
||||
use serai_primitives::{coin::*, balance::*, address::*, instructions::*};
|
||||
|
||||
pub type CoinsEvent = crate::Event<Test, ()>;
|
||||
use crate::mock::*;
|
||||
|
||||
pub type CoinsEvent = crate::Event<Test, crate::CoinsInstance>;
|
||||
|
||||
#[test]
|
||||
fn mint() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// minting u64::MAX should work
|
||||
let coin = Coin::Serai;
|
||||
let to = insecure_pair_from_name("random1").public();
|
||||
let to = Pair::generate().0.public();
|
||||
let balance = Balance { coin, amount: Amount(u64::MAX) };
|
||||
|
||||
Coins::mint(to, balance).unwrap();
|
||||
@@ -22,7 +22,7 @@ fn mint() {
|
||||
assert!(Coins::mint(to, Balance { coin, amount: Amount(1) }).is_err());
|
||||
|
||||
// supply now should be equal to sum of the accounts balance sum
|
||||
assert_eq!(Coins::supply(coin), balance.amount.0);
|
||||
assert_eq!(Coins::supply(coin), balance.amount);
|
||||
|
||||
// test events
|
||||
let mint_events = System::events()
|
||||
@@ -49,19 +49,19 @@ fn burn_with_instruction() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// mint some coin
|
||||
let coin = Coin::External(ExternalCoin::Bitcoin);
|
||||
let to = insecure_pair_from_name("random1").public();
|
||||
let to = Pair::generate().0.public();
|
||||
let balance = Balance { coin, amount: Amount(10 * 10u64.pow(coin.decimals())) };
|
||||
|
||||
Coins::mint(to, balance).unwrap();
|
||||
assert_eq!(Coins::balance(to, coin), balance.amount);
|
||||
assert_eq!(Coins::supply(coin), balance.amount.0);
|
||||
assert_eq!(Coins::supply(coin), balance.amount);
|
||||
|
||||
// we shouldn't be able to burn more than what we have
|
||||
let mut instruction = OutInstructionWithBalance {
|
||||
instruction: OutInstruction { address: ExternalAddress::new(vec![]).unwrap() },
|
||||
instruction: OutInstruction::Transfer(ExternalAddress::try_from(vec![]).unwrap()),
|
||||
balance: ExternalBalance {
|
||||
coin: coin.try_into().unwrap(),
|
||||
amount: Amount(balance.amount.0 + 1),
|
||||
amount: (balance.amount + Amount(1)).unwrap(),
|
||||
},
|
||||
};
|
||||
assert!(
|
||||
@@ -74,7 +74,7 @@ fn burn_with_instruction() {
|
||||
|
||||
// balance & supply now should be back to 0
|
||||
assert_eq!(Coins::balance(to, coin), Amount(0));
|
||||
assert_eq!(Coins::supply(coin), 0);
|
||||
assert_eq!(Coins::supply(coin), Amount(0));
|
||||
|
||||
let burn_events = System::events()
|
||||
.iter()
|
||||
@@ -100,19 +100,19 @@ fn transfer() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// mint some coin
|
||||
let coin = Coin::External(ExternalCoin::Bitcoin);
|
||||
let from = insecure_pair_from_name("random1").public();
|
||||
let from = Pair::generate().0.public();
|
||||
let balance = Balance { coin, amount: Amount(10 * 10u64.pow(coin.decimals())) };
|
||||
|
||||
Coins::mint(from, balance).unwrap();
|
||||
assert_eq!(Coins::balance(from, coin), balance.amount);
|
||||
assert_eq!(Coins::supply(coin), balance.amount.0);
|
||||
assert_eq!(Coins::supply(coin), balance.amount);
|
||||
|
||||
// we can't send more than what we have
|
||||
let to = insecure_pair_from_name("random2").public();
|
||||
let to = Pair::generate().0.public();
|
||||
assert!(Coins::transfer(
|
||||
RawOrigin::Signed(from).into(),
|
||||
to,
|
||||
Balance { coin, amount: Amount(balance.amount.0 + 1) }
|
||||
Balance { coin, amount: (balance.amount + Amount(1)).unwrap() }
|
||||
)
|
||||
.is_err());
|
||||
|
||||
@@ -124,6 +124,6 @@ fn transfer() {
|
||||
assert_eq!(Coins::balance(to, coin), balance.amount);
|
||||
|
||||
// supply shouldn't change
|
||||
assert_eq!(Coins::supply(coin), balance.amount.0);
|
||||
assert_eq!(Coins::supply(coin), balance.amount);
|
||||
})
|
||||
}
|
||||
@@ -3,7 +3,7 @@ name = "serai-dex-pallet"
|
||||
version = "0.1.0"
|
||||
description = "DEX pallet for Serai"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/dex/pallet"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/dex"
|
||||
authors = ["Parity Technologies <admin@parity.io>, Akil Demir <akildemir72@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.80"
|
||||
@@ -22,19 +22,19 @@ workspace = true
|
||||
scale = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
|
||||
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
|
||||
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-io = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-api = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-std = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-io = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-api = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-core = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
|
||||
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
frame-benchmarking = { git = "https://github.com/serai-dex/substrate", default-features = false, optional = true }
|
||||
frame-system = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
frame-benchmarking = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false, optional = true }
|
||||
|
||||
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
|
||||
coins-pallet = { package = "serai-coins-pallet", path = "../coins", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../../primitives", default-features = false }
|
||||
serai-primitives = { path = "../primitives", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
rand_core = { version = "0.6", default-features = false, features = ["getrandom"] }
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
use super::*;
|
||||
use frame_benchmarking::{benchmarks, whitelisted_caller};
|
||||
use frame_support::{assert_ok, storage::bounded_vec::BoundedVec};
|
||||
use frame_support::{assert_ok, storage::bounded::BoundedVec};
|
||||
use frame_system::RawOrigin as SystemOrigin;
|
||||
|
||||
use sp_runtime::traits::StaticLookup;
|
||||
77
substrate/economic-security/Cargo.toml
Normal file
77
substrate/economic-security/Cargo.toml
Normal file
@@ -0,0 +1,77 @@
|
||||
[package]
|
||||
name = "serai-economic-security-pallet"
|
||||
version = "0.1.0"
|
||||
description = "Economic Security pallet for Serai"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/economic-security"
|
||||
authors = ["Akil Demir <akildemir72@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.80"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["scale", "scale-info"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
frame-system = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
|
||||
dex-pallet = { package = "serai-dex-pallet", path = "../dex", default-features = false }
|
||||
coins-pallet = { package = "serai-coins-pallet", path = "../coins", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../primitives", default-features = false }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
pallet-babe = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
pallet-grandpa = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
pallet-timestamp = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
|
||||
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets", default-features = false }
|
||||
|
||||
sp-io = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-core = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-consensus-babe = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"scale/std",
|
||||
"scale-info/std",
|
||||
|
||||
"frame-system/std",
|
||||
"frame-support/std",
|
||||
|
||||
"sp-io/std",
|
||||
"sp-core/std",
|
||||
"sp-consensus-babe/std",
|
||||
|
||||
"dex-pallet/std",
|
||||
"coins-pallet/std",
|
||||
"validator-sets-pallet/std",
|
||||
|
||||
"serai-primitives/std",
|
||||
|
||||
"pallet-babe/std",
|
||||
"pallet-grandpa/std",
|
||||
"pallet-timestamp/std",
|
||||
]
|
||||
|
||||
try-runtime = [
|
||||
"frame-system/try-runtime",
|
||||
"frame-support/try-runtime",
|
||||
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
|
||||
|
||||
default = ["std"]
|
||||
@@ -1,77 +0,0 @@
|
||||
[package]
|
||||
name = "serai-economic-security-pallet"
|
||||
version = "0.1.0"
|
||||
description = "Economic Security pallet for Serai"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/economic-security/pallet"
|
||||
authors = ["Akil Demir <akildemir72@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.80"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["scale", "scale-info"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false }
|
||||
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../../primitives", default-features = false }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
pallet-babe = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
pallet-grandpa = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
pallet-timestamp = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false }
|
||||
|
||||
sp-io = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-consensus-babe = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"scale/std",
|
||||
"scale-info/std",
|
||||
|
||||
"frame-system/std",
|
||||
"frame-support/std",
|
||||
|
||||
"sp-io/std",
|
||||
"sp-core/std",
|
||||
"sp-consensus-babe/std",
|
||||
|
||||
"dex-pallet/std",
|
||||
"coins-pallet/std",
|
||||
"validator-sets-pallet/std",
|
||||
|
||||
"serai-primitives/std",
|
||||
|
||||
"pallet-babe/std",
|
||||
"pallet-grandpa/std",
|
||||
"pallet-timestamp/std",
|
||||
]
|
||||
|
||||
try-runtime = [
|
||||
"frame-system/try-runtime",
|
||||
"frame-support/try-runtime",
|
||||
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
|
||||
|
||||
default = ["std"]
|
||||
@@ -3,7 +3,7 @@ name = "serai-emissions-pallet"
|
||||
version = "0.1.0"
|
||||
description = "Emissions pallet for Serai"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/emissions/pallet"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/emissions"
|
||||
authors = ["Akil Demir <akildemir72@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.80"
|
||||
@@ -22,22 +22,20 @@ workspace = true
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
frame-system = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-std = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
|
||||
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
|
||||
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false }
|
||||
dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false }
|
||||
genesis-liquidity-pallet = { package = "serai-genesis-liquidity-pallet", path = "../../genesis-liquidity/pallet", default-features = false }
|
||||
coins-pallet = { package = "serai-coins-pallet", path = "../coins", default-features = false }
|
||||
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets", default-features = false }
|
||||
dex-pallet = { package = "serai-dex-pallet", path = "../dex", default-features = false }
|
||||
genesis-liquidity-pallet = { package = "serai-genesis-liquidity-pallet", path = "../genesis-liquidity", default-features = false }
|
||||
|
||||
economic-security-pallet = { package = "serai-economic-security-pallet", path = "../../economic-security/pallet", default-features = false }
|
||||
economic-security-pallet = { package = "serai-economic-security-pallet", path = "../economic-security", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../../primitives", default-features = false }
|
||||
validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../../validator-sets/primitives", default-features = false }
|
||||
emissions-primitives = { package = "serai-emissions-primitives", path = "../primitives", default-features = false }
|
||||
serai-primitives = { path = "../primitives", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
@@ -58,8 +56,6 @@ std = [
|
||||
"economic-security-pallet/std",
|
||||
|
||||
"serai-primitives/std",
|
||||
"emissions-primitives/std",
|
||||
]
|
||||
fast-epoch = []
|
||||
try-runtime = [] # TODO
|
||||
default = ["std"]
|
||||
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "serai-emissions-primitives"
|
||||
version = "0.1.0"
|
||||
description = "Serai emissions primitives"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/emissions/primitives"
|
||||
authors = ["Akil Demir <akildemir72@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.80"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
serai-primitives = { path = "../../primitives", default-features = false }
|
||||
|
||||
[features]
|
||||
std = ["serai-primitives/std"]
|
||||
default = ["std"]
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Luke Parker
|
||||
|
||||
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.
|
||||
@@ -1,26 +0,0 @@
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use serai_primitives::{DAYS, YEARS, SeraiAddress, system_address};
|
||||
|
||||
// Protocol owned liquidity account.
|
||||
pub const POL_ACCOUNT: SeraiAddress = system_address(b"Serai-protocol_owned_liquidity");
|
||||
|
||||
/// INITIAL_REWARD = 100,000 SRI / BLOCKS_PER_DAY for 60 days
|
||||
pub const INITIAL_REWARD_PER_BLOCK: u64 = (100_000 * 10u64.pow(8)) / DAYS;
|
||||
|
||||
/// REWARD = 20M SRI / BLOCKS_PER_YEAR
|
||||
pub const REWARD_PER_BLOCK: u64 = (20_000_000 * 10u64.pow(8)) / YEARS;
|
||||
|
||||
/// 20% of all stake desired to be for Serai network
|
||||
pub const SERAI_VALIDATORS_DESIRED_PERCENTAGE: u64 = 20;
|
||||
|
||||
/// Desired unused capacity ratio for a network assuming capacity is 10,000.
|
||||
pub const DESIRED_DISTRIBUTION: u64 = 1_000;
|
||||
|
||||
/// Percentage scale for the validator vs. pool reward distribution.
|
||||
pub const ACCURACY_MULTIPLIER: u64 = 10_000;
|
||||
|
||||
/// The block to target for economic security
|
||||
pub const SECURE_BY: u64 = YEARS;
|
||||
62
substrate/genesis-liquidity/Cargo.toml
Normal file
62
substrate/genesis-liquidity/Cargo.toml
Normal file
@@ -0,0 +1,62 @@
|
||||
[package]
|
||||
name = "serai-genesis-liquidity-pallet"
|
||||
version = "0.1.0"
|
||||
description = "Genesis liquidity pallet for Serai"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/genesis-liquidity"
|
||||
authors = ["Akil Demir <akildemir72@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.80"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["scale", "scale-info"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
frame-system = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
|
||||
sp-std = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-core = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-application-crypto = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
|
||||
dex-pallet = { package = "serai-dex-pallet", path = "../dex", default-features = false }
|
||||
coins-pallet = { package = "serai-coins-pallet", path = "../coins", default-features = false }
|
||||
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets", default-features = false }
|
||||
|
||||
economic-security-pallet = { package = "serai-economic-security-pallet", path = "../economic-security", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../primitives", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"scale/std",
|
||||
"scale-info/std",
|
||||
|
||||
"frame-system/std",
|
||||
"frame-support/std",
|
||||
|
||||
"sp-std/std",
|
||||
"sp-core/std",
|
||||
"sp-application-crypto/std",
|
||||
|
||||
"coins-pallet/std",
|
||||
"dex-pallet/std",
|
||||
"validator-sets-pallet/std",
|
||||
|
||||
"economic-security-pallet/std",
|
||||
|
||||
"serai-primitives/std",
|
||||
]
|
||||
try-runtime = [] # TODO
|
||||
|
||||
default = ["std"]
|
||||
@@ -1,67 +0,0 @@
|
||||
[package]
|
||||
name = "serai-genesis-liquidity-pallet"
|
||||
version = "0.1.0"
|
||||
description = "Genesis liquidity pallet for Serai"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/genesis-liquidity/pallet"
|
||||
authors = ["Akil Demir <akildemir72@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.80"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["scale", "scale-info"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false }
|
||||
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
|
||||
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false }
|
||||
|
||||
economic-security-pallet = { package = "serai-economic-security-pallet", path = "../../economic-security/pallet", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../../primitives", default-features = false }
|
||||
genesis-liquidity-primitives = { package = "serai-genesis-liquidity-primitives", path = "../primitives", default-features = false }
|
||||
validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../../validator-sets/primitives", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"scale/std",
|
||||
"scale-info/std",
|
||||
|
||||
"frame-system/std",
|
||||
"frame-support/std",
|
||||
|
||||
"sp-std/std",
|
||||
"sp-core/std",
|
||||
"sp-application-crypto/std",
|
||||
|
||||
"coins-pallet/std",
|
||||
"dex-pallet/std",
|
||||
"validator-sets-pallet/std",
|
||||
|
||||
"economic-security-pallet/std",
|
||||
|
||||
"serai-primitives/std",
|
||||
"genesis-liquidity-primitives/std",
|
||||
"validator-sets-primitives/std",
|
||||
]
|
||||
try-runtime = [] # TODO
|
||||
fast-epoch = []
|
||||
|
||||
default = ["std"]
|
||||
@@ -1,45 +0,0 @@
|
||||
[package]
|
||||
name = "serai-genesis-liquidity-primitives"
|
||||
version = "0.1.0"
|
||||
description = "Serai genesis liquidity primitives"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/genesis-liquidity/primitives"
|
||||
authors = ["Akil Demir <akildemir72@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.80"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
zeroize = { version = "^1.5", features = ["derive"], optional = true }
|
||||
|
||||
borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"], optional = true }
|
||||
serde = { version = "1", default-features = false, features = ["derive", "alloc"], optional = true }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../../primitives", default-features = false }
|
||||
validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../../validator-sets/primitives", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"zeroize",
|
||||
"scale/std",
|
||||
"borsh?/std",
|
||||
"serde?/std",
|
||||
"scale-info/std",
|
||||
|
||||
"serai-primitives/std",
|
||||
"validator-sets-primitives/std",
|
||||
|
||||
"sp-std/std"
|
||||
]
|
||||
default = ["std"]
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Luke Parker
|
||||
|
||||
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.
|
||||
@@ -1,54 +0,0 @@
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[cfg(feature = "borsh")]
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
use scale::{Encode, Decode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
use serai_primitives::*;
|
||||
use validator_sets_primitives::ValidatorSet;
|
||||
|
||||
pub const INITIAL_GENESIS_LP_SHARES: u64 = 10_000;
|
||||
|
||||
// This is the account to hold and manage the genesis liquidity.
|
||||
pub const GENESIS_LIQUIDITY_ACCOUNT: SeraiAddress = system_address(b"GenesisLiquidity-account");
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct Values {
|
||||
pub monero: u64,
|
||||
pub ether: u64,
|
||||
pub dai: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct LiquidityAmount {
|
||||
pub shares: u64,
|
||||
pub coins: u64,
|
||||
}
|
||||
|
||||
impl LiquidityAmount {
|
||||
pub fn zero() -> Self {
|
||||
LiquidityAmount { shares: 0, coins: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
/// The message for the oraclize_values signature.
|
||||
pub fn oraclize_values_message(set: &ValidatorSet, values: &Values) -> Vec<u8> {
|
||||
(b"GenesisLiquidity-oraclize_values", set, values).encode()
|
||||
}
|
||||
88
substrate/in-instructions/Cargo.toml
Normal file
88
substrate/in-instructions/Cargo.toml
Normal file
@@ -0,0 +1,88 @@
|
||||
[package]
|
||||
name = "serai-in-instructions-pallet"
|
||||
version = "0.1.0"
|
||||
description = "Execute calls via In Instructions from unsigned transactions"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/genesis-liquidity"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
rust-version = "1.80"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["scale", "scale-info"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
bitvec = { version = "1", default-features = false, features = ["alloc"] }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "max-encoded-len"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
sp-std = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-application-crypto = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-io = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
sp-core = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
|
||||
frame-system = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../primitives", default-features = false }
|
||||
|
||||
coins-pallet = { package = "serai-coins-pallet", path = "../coins", default-features = false }
|
||||
dex-pallet = { package = "serai-dex-pallet", path = "../dex", default-features = false }
|
||||
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets", default-features = false }
|
||||
genesis-liquidity-pallet = { package = "serai-genesis-liquidity-pallet", path = "../genesis-liquidity", default-features = false }
|
||||
emissions-pallet = { package = "serai-emissions-pallet", path = "../emissions", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
pallet-babe = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
pallet-grandpa = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
pallet-timestamp = { git = "https://github.com/serai-dex/polkadot-sdk", branch = "serai-next", default-features = false }
|
||||
|
||||
economic-security-pallet = { package = "serai-economic-security-pallet", path = "../economic-security", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"scale/std",
|
||||
"scale-info/std",
|
||||
|
||||
"sp-std/std",
|
||||
"sp-application-crypto/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-core/std",
|
||||
|
||||
"frame-system/std",
|
||||
"frame-support/std",
|
||||
|
||||
"serai-primitives/std",
|
||||
|
||||
"coins-pallet/std",
|
||||
"dex-pallet/std",
|
||||
"validator-sets-pallet/std",
|
||||
"genesis-liquidity-pallet/std",
|
||||
"emissions-pallet/std",
|
||||
|
||||
"economic-security-pallet/std",
|
||||
|
||||
"pallet-babe/std",
|
||||
"pallet-grandpa/std",
|
||||
"pallet-timestamp/std",
|
||||
]
|
||||
|
||||
try-runtime = [
|
||||
"frame-system/try-runtime",
|
||||
"frame-support/try-runtime",
|
||||
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
|
||||
default = ["std"]
|
||||
@@ -1,89 +0,0 @@
|
||||
[package]
|
||||
name = "serai-in-instructions-pallet"
|
||||
version = "0.1.0"
|
||||
description = "Execute calls via In Instructions from unsigned transactions"
|
||||
license = "AGPL-3.0-only"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
rust-version = "1.80"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["scale", "scale-info"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
bitvec = { version = "1", default-features = false, features = ["alloc"] }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "max-encoded-len"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-io = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../../primitives", default-features = false }
|
||||
in-instructions-primitives = { package = "serai-in-instructions-primitives", path = "../primitives", default-features = false }
|
||||
|
||||
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
|
||||
dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false }
|
||||
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false }
|
||||
genesis-liquidity-pallet = { package = "serai-genesis-liquidity-pallet", path = "../../genesis-liquidity/pallet", default-features = false }
|
||||
emissions-pallet = { package = "serai-emissions-pallet", path = "../../emissions/pallet", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
pallet-babe = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
pallet-grandpa = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
pallet-timestamp = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
economic-security-pallet = { package = "serai-economic-security-pallet", path = "../../economic-security/pallet", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"scale/std",
|
||||
"scale-info/std",
|
||||
|
||||
"sp-std/std",
|
||||
"sp-application-crypto/std",
|
||||
"sp-io/std",
|
||||
"sp-runtime/std",
|
||||
"sp-core/std",
|
||||
|
||||
"frame-system/std",
|
||||
"frame-support/std",
|
||||
|
||||
"serai-primitives/std",
|
||||
"in-instructions-primitives/std",
|
||||
|
||||
"coins-pallet/std",
|
||||
"dex-pallet/std",
|
||||
"validator-sets-pallet/std",
|
||||
"genesis-liquidity-pallet/std",
|
||||
"emissions-pallet/std",
|
||||
|
||||
"economic-security-pallet/std",
|
||||
|
||||
"pallet-babe/std",
|
||||
"pallet-grandpa/std",
|
||||
"pallet-timestamp/std",
|
||||
]
|
||||
|
||||
try-runtime = [
|
||||
"frame-system/try-runtime",
|
||||
"frame-support/try-runtime",
|
||||
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
|
||||
default = ["std"]
|
||||
@@ -1,52 +0,0 @@
|
||||
[package]
|
||||
name = "serai-in-instructions-primitives"
|
||||
version = "0.1.0"
|
||||
description = "Serai instructions library, enabling encoding and decoding"
|
||||
license = "MIT"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.80"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
zeroize = { version = "^1.5", features = ["derive"], optional = true }
|
||||
|
||||
borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"], optional = true }
|
||||
serde = { version = "1", default-features = false, features = ["derive", "alloc"], optional = true }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../../primitives", default-features = false }
|
||||
coins-primitives = { package = "serai-coins-primitives", path = "../../coins/primitives", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"zeroize",
|
||||
|
||||
"borsh?/std",
|
||||
"serde?/std",
|
||||
|
||||
"scale/std",
|
||||
"scale-info/std",
|
||||
|
||||
"sp-std/std",
|
||||
"sp-application-crypto/std",
|
||||
"sp-runtime/std",
|
||||
|
||||
"serai-primitives/std",
|
||||
"coins-primitives/std",
|
||||
]
|
||||
borsh = ["dep:borsh", "serai-primitives/borsh", "coins-primitives/borsh"]
|
||||
serde = ["dep:serde", "serai-primitives/serde", "coins-primitives/serde"]
|
||||
default = ["std"]
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022-2023 Luke Parker
|
||||
|
||||
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.
|
||||
@@ -1,141 +0,0 @@
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[cfg(feature = "borsh")]
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use scale::{Encode, Decode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
use sp_application_crypto::sr25519::Signature;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use sp_std::vec::Vec;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
|
||||
#[rustfmt::skip]
|
||||
use serai_primitives::{BlockHash, ExternalNetworkId, NetworkId, ExternalBalance, Balance, SeraiAddress, ExternalAddress, system_address};
|
||||
|
||||
mod shorthand;
|
||||
pub use shorthand::*;
|
||||
|
||||
pub const MAX_BATCH_SIZE: usize = 25_000; // ~25kb
|
||||
|
||||
// This is the account which will be the origin for any dispatched `InInstruction`s.
|
||||
pub const IN_INSTRUCTION_EXECUTOR: SeraiAddress = system_address(b"InInstructions-executor");
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum OutAddress {
|
||||
Serai(SeraiAddress),
|
||||
External(ExternalAddress),
|
||||
}
|
||||
|
||||
impl OutAddress {
|
||||
pub fn is_native(&self) -> bool {
|
||||
matches!(self, Self::Serai(_))
|
||||
}
|
||||
|
||||
pub fn as_native(self) -> Option<SeraiAddress> {
|
||||
match self {
|
||||
Self::Serai(addr) => Some(addr),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_external(self) -> Option<ExternalAddress> {
|
||||
match self {
|
||||
Self::External(addr) => Some(addr),
|
||||
Self::Serai(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum DexCall {
|
||||
// address to send the lp tokens to
|
||||
// TODO: Update this per documentation/Shorthand
|
||||
SwapAndAddLiquidity(SeraiAddress),
|
||||
// minimum out balance and out address
|
||||
Swap(Balance, OutAddress),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum InInstruction {
|
||||
Transfer(SeraiAddress),
|
||||
Dex(DexCall),
|
||||
GenesisLiquidity(SeraiAddress),
|
||||
SwapToStakedSRI(SeraiAddress, NetworkId),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct RefundableInInstruction {
|
||||
pub origin: Option<ExternalAddress>,
|
||||
pub instruction: InInstruction,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct InInstructionWithBalance {
|
||||
pub instruction: InInstruction,
|
||||
pub balance: ExternalBalance,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, TypeInfo, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct Batch {
|
||||
pub network: ExternalNetworkId,
|
||||
pub id: u32,
|
||||
pub external_network_block_hash: BlockHash,
|
||||
pub instructions: Vec<InInstructionWithBalance>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, TypeInfo, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct SignedBatch {
|
||||
pub batch: Batch,
|
||||
#[cfg_attr(
|
||||
feature = "borsh",
|
||||
borsh(
|
||||
serialize_with = "serai_primitives::borsh_serialize_signature",
|
||||
deserialize_with = "serai_primitives::borsh_deserialize_signature"
|
||||
)
|
||||
)]
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl Zeroize for SignedBatch {
|
||||
fn zeroize(&mut self) {
|
||||
self.batch.zeroize();
|
||||
self.signature.as_mut().zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make this an associated method?
|
||||
/// The message for the batch signature.
|
||||
pub fn batch_message(batch: &Batch) -> Vec<u8> {
|
||||
[b"InInstructions-batch".as_ref(), &batch.encode()].concat()
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
#[cfg(feature = "std")]
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[cfg(feature = "borsh")]
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use scale::{Encode, Decode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
use serai_primitives::{Amount, ExternalAddress, ExternalCoin, SeraiAddress};
|
||||
|
||||
use coins_primitives::OutInstruction;
|
||||
|
||||
use crate::RefundableInInstruction;
|
||||
#[cfg(feature = "std")]
|
||||
use crate::InInstruction;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||
#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Shorthand {
|
||||
Raw(RefundableInInstruction),
|
||||
Swap {
|
||||
origin: Option<ExternalAddress>,
|
||||
coin: ExternalCoin,
|
||||
minimum: Amount,
|
||||
out: OutInstruction,
|
||||
},
|
||||
SwapAndAddLiquidity {
|
||||
origin: Option<ExternalAddress>,
|
||||
minimum: Amount,
|
||||
gas: Amount,
|
||||
address: SeraiAddress,
|
||||
},
|
||||
}
|
||||
|
||||
impl Shorthand {
|
||||
#[cfg(feature = "std")]
|
||||
pub fn transfer(origin: Option<ExternalAddress>, address: SeraiAddress) -> Self {
|
||||
Self::Raw(RefundableInInstruction { origin, instruction: InInstruction::Transfer(address) })
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Shorthand> for RefundableInInstruction {
|
||||
type Error = &'static str;
|
||||
fn try_from(shorthand: Shorthand) -> Result<RefundableInInstruction, &'static str> {
|
||||
Ok(match shorthand {
|
||||
Shorthand::Raw(instruction) => instruction,
|
||||
Shorthand::Swap { .. } => todo!(),
|
||||
Shorthand::SwapAndAddLiquidity { .. } => todo!(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -381,7 +381,7 @@ pub mod pallet {
|
||||
|
||||
// Explicitly provide a pre-dispatch which calls validate_unsigned
|
||||
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
|
||||
Self::validate_unsigned(TransactionSource::InBlock, call).map(|_| ()).map_err(Into::into)
|
||||
Self::validate_unsigned(TransactionSource::InBlock, call).map(|_| ())
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user