mirror of
https://github.com/serai-dex/serai.git
synced 2024-11-16 17:07:35 +00:00
Initial In Instructions pallet and Serai client lib (#233)
* Initial work on an In Inherents pallet * Add an event for when a batch is executed * Add a dummy provider for InInstructions * Add in-instructions to the node * Add the Serai runtime API to the processor * Move processor tests around * Build a subxt Client around Serai * Successfully get Batch events from Serai Renamed processor/substrate to processor/serai. * Much more robust InInstruction pallet * Implement the workaround from https://github.com/paritytech/subxt/issues/602 * Initial prototype of processor generated InInstructions * Correct PendingCoins data flow for InInstructions * Minor lint to in-instructions * Remove the global Serai connection for a partial re-impl * Correct ID handling of the processor test * Workaround the delay in the subscription * Make an unwrap an if let Some, remove old comments * Lint the processor toml * Rebase and update * Move substrate/in-instructions to substrate/in-instructions/pallet * Start an in-instructions primitives lib * Properly update processor to subxt 0.24 Also corrects failures from the rebase. * in-instructions cargo update * Implement IsFatalError * is_inherent -> true * Rename in-instructions crates and misc cleanup * Update documentation * cargo update * Misc update fixes * Replace height with block_number * Update processor src to latest subxt * Correct pipeline for InInstructions testing * Remove runtime::AccountId for serai_primitives::NativeAddress * Rewrite the in-instructions pallet Complete with respect to the currently written docs. Drops the custom serializer for just using SCALE. Makes slight tweaks as relevant. * Move instructions' InherentDataProvider to a client crate * Correct doc gen * Add serde to in-instructions-primitives * Add in-instructions-primitives to pallet * Heights -> BlockNumbers * Get batch pub test loop working * Update in instructions pallet terminology Removes the ambiguous Coin for Update. Removes pending/artificial latency for furture client work. Also moves to using serai_primitives::Coin. * Add a BlockNumber primitive * Belated cargo fmt * Further document why DifferentBatch isn't fatal * Correct processor sleeps * Remove metadata at compile time, add test framework for Serai nodes * Remove manual RPC client * Simplify update test * Improve re-exporting behavior of serai-runtime It now re-exports all pallets underneath it. * Add a function to get storage values to the Serai RPC * Update substrate/ to latest substrate * Create a dedicated crate for the Serai RPC * Remove unused dependencies in substrate/ * Remove unused dependencies in coins/ Out of scope for this branch, just minor and path of least resistance. * Use substrate/serai/client for the Serai RPC lib It's a bit out of place, since these client folders are intended for the node to access pallets and so on. This is for end-users to access Serai as a whole. In that sense, it made more sense as a top level folder, yet that also felt out of place. * Move InInstructions test to serai-client for now * Final cleanup * Update deny.toml * Cargo.lock update from merging develop * Update nightly Attempt to work around the current CI failure, which is a Rust ICE. We previously didn't upgrade due to clippy 10134, yet that's been reverted. * clippy * clippy * fmt * NativeAddress -> SeraiAddress * Sec fix on non-provided updates and doc fixes * Add Serai as a Coin Necessary in order to swap to Serai. * Add a BlockHash type, used for batch IDs * Remove origin from InInstruction Makes InInstructionTarget. Adds RefundableInInstruction with origin. * Document storage items in in-instructions * Rename serai/client/tests/serai.rs to updates.rs It only tested publishing updates and their successful acceptance.
This commit is contained in:
parent
e13cf52c49
commit
8ca90e7905
53 changed files with 1613 additions and 403 deletions
15
.github/actions/test-dependencies/action.yml
vendored
15
.github/actions/test-dependencies/action.yml
vendored
|
@ -12,6 +12,11 @@ inputs:
|
|||
required: false
|
||||
default: v0.18.0.0
|
||||
|
||||
serai:
|
||||
description: "Run a Serai development node in the background"
|
||||
required: false
|
||||
default: false
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
|
@ -32,3 +37,13 @@ runs:
|
|||
|
||||
- name: Run a Monero Wallet-RPC
|
||||
uses: ./.github/actions/monero-wallet-rpc
|
||||
|
||||
- name: Run a Serai Development Node
|
||||
if: ${{ inputs.serai }}
|
||||
shell: bash
|
||||
run: |
|
||||
cd substrate/node
|
||||
cargo build
|
||||
cd ../..
|
||||
|
||||
./target/debug/serai-node --dev &
|
||||
|
|
2
.github/nightly-version
vendored
2
.github/nightly-version
vendored
|
@ -1 +1 @@
|
|||
nightly-2022-12-01
|
||||
nightly-2023-01-16
|
||||
|
|
5
.github/workflows/tests.yml
vendored
5
.github/workflows/tests.yml
vendored
|
@ -58,6 +58,11 @@ jobs:
|
|||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build node
|
||||
run: |
|
||||
cd substrate/node
|
||||
cargo build
|
||||
|
||||
- name: Run Tests
|
||||
run: cargo test --all-features
|
||||
|
||||
|
|
254
Cargo.lock
generated
254
Cargo.lock
generated
|
@ -1698,6 +1698,17 @@ dependencies = [
|
|||
"rusticata-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derivative"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.11.2"
|
||||
|
@ -2170,7 +2181,6 @@ dependencies = [
|
|||
"ethers-solc",
|
||||
"eyre",
|
||||
"group",
|
||||
"hex-literal",
|
||||
"k256",
|
||||
"modular-frost",
|
||||
"rand_core 0.6.4",
|
||||
|
@ -3414,6 +3424,7 @@ dependencies = [
|
|||
"rustls-native-certs",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3547,6 +3558,46 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "in-instructions-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"in-instructions-pallet",
|
||||
"jsonrpsee-core",
|
||||
"jsonrpsee-http-client",
|
||||
"parity-scale-codec",
|
||||
"sp-inherents",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "in-instructions-pallet"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"in-instructions-primitives",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"serai-primitives",
|
||||
"serde",
|
||||
"sp-inherents",
|
||||
"sp-runtime",
|
||||
"sp-std",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "in-instructions-primitives"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"serai-primitives",
|
||||
"serde",
|
||||
"sp-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.3"
|
||||
|
@ -3731,13 +3782,36 @@ version = "0.16.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d291e3a5818a2384645fd9756362e6d89cf0541b0b916fa7702ea4a9833608e"
|
||||
dependencies = [
|
||||
"jsonrpsee-client-transport",
|
||||
"jsonrpsee-core",
|
||||
"jsonrpsee-http-client",
|
||||
"jsonrpsee-proc-macros",
|
||||
"jsonrpsee-server",
|
||||
"jsonrpsee-types",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpsee-client-transport"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "965de52763f2004bc91ac5bcec504192440f0b568a5d621c59d9dbd6f886c3fb"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"jsonrpsee-core",
|
||||
"jsonrpsee-types",
|
||||
"pin-project",
|
||||
"rustls-native-certs",
|
||||
"soketto",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpsee-core"
|
||||
version = "0.16.2"
|
||||
|
@ -3746,9 +3820,11 @@ checksum = "a4e70b4439a751a5de7dd5ed55eacff78ebf4ffe0fc009cb1ebb11417f5b536b"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"arrayvec 0.7.2",
|
||||
"async-lock",
|
||||
"async-trait",
|
||||
"beef",
|
||||
"futures-channel",
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
"globset",
|
||||
"hyper",
|
||||
|
@ -3764,6 +3840,25 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpsee-http-client"
|
||||
version = "0.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc345b0a43c6bc49b947ebeb936e886a419ee3d894421790c969cc56040542ad"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"jsonrpsee-core",
|
||||
"jsonrpsee-types",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpsee-proc-macros"
|
||||
version = "0.16.2"
|
||||
|
@ -4832,7 +4927,6 @@ name = "monero-serai"
|
|||
version = "0.1.2-alpha"
|
||||
dependencies = [
|
||||
"base58-monero",
|
||||
"blake2",
|
||||
"curve25519-dalek 3.2.0",
|
||||
"dalek-ff-group",
|
||||
"digest_auth",
|
||||
|
@ -7477,12 +7571,10 @@ dependencies = [
|
|||
"sc-block-builder",
|
||||
"sc-client-api",
|
||||
"sc-consensus",
|
||||
"sc-executor",
|
||||
"sc-network",
|
||||
"sc-network-common",
|
||||
"sc-network-gossip",
|
||||
"sc-service",
|
||||
"sc-transaction-pool",
|
||||
"sp-api",
|
||||
"sp-application-crypto",
|
||||
"sp-blockchain",
|
||||
|
@ -7594,6 +7686,29 @@ dependencies = [
|
|||
"prometheus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scale-bits"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8dd7aca73785181cc41f0bbe017263e682b585ca660540ba569133901d013ecf"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scale-decode"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d823d4be477fc33321f93d08fb6c2698273d044f01362dc27573a750deb7c233"
|
||||
dependencies = [
|
||||
"parity-scale-codec",
|
||||
"scale-bits",
|
||||
"scale-info",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scale-info"
|
||||
version = "2.3.1"
|
||||
|
@ -7620,6 +7735,23 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scale-value"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16a5e7810815bd295da73e4216d1dfbced3c7c7c7054d70fa5f6e4c58123fff4"
|
||||
dependencies = [
|
||||
"either",
|
||||
"frame-metadata",
|
||||
"parity-scale-codec",
|
||||
"scale-bits",
|
||||
"scale-decode",
|
||||
"scale-info",
|
||||
"serde",
|
||||
"thiserror",
|
||||
"yap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.21"
|
||||
|
@ -7823,6 +7955,23 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "930c0acf610d3fdb5e2ab6213019aaa04e227ebe9547b0649ba599b16d788bd7"
|
||||
|
||||
[[package]]
|
||||
name = "serai-client"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"in-instructions-primitives",
|
||||
"jsonrpsee-server",
|
||||
"lazy_static",
|
||||
"parity-scale-codec",
|
||||
"scale-value",
|
||||
"serai-primitives",
|
||||
"serai-runtime",
|
||||
"serde",
|
||||
"subxt",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serai-node"
|
||||
version = "0.1.0"
|
||||
|
@ -7831,12 +7980,8 @@ dependencies = [
|
|||
"clap 4.1.1",
|
||||
"frame-benchmarking",
|
||||
"frame-benchmarking-cli",
|
||||
"frame-system",
|
||||
"futures",
|
||||
"in-instructions-client",
|
||||
"jsonrpsee",
|
||||
"log",
|
||||
"pallet-tendermint",
|
||||
"pallet-transaction-payment",
|
||||
"pallet-transaction-payment-rpc",
|
||||
"sc-basic-authorship",
|
||||
"sc-cli",
|
||||
|
@ -7844,31 +7989,24 @@ dependencies = [
|
|||
"sc-client-db",
|
||||
"sc-consensus",
|
||||
"sc-executor",
|
||||
"sc-keystore",
|
||||
"sc-network",
|
||||
"sc-rpc",
|
||||
"sc-rpc-api",
|
||||
"sc-service",
|
||||
"sc-telemetry",
|
||||
"sc-tendermint",
|
||||
"sc-transaction-pool",
|
||||
"sc-transaction-pool-api",
|
||||
"serai-primitives",
|
||||
"serai-runtime",
|
||||
"sp-api",
|
||||
"sp-application-crypto",
|
||||
"sp-block-builder",
|
||||
"sp-blockchain",
|
||||
"sp-consensus",
|
||||
"sp-core",
|
||||
"sp-inherents",
|
||||
"sp-keyring",
|
||||
"sp-keystore",
|
||||
"sp-runtime",
|
||||
"sp-tendermint",
|
||||
"substrate-build-script-utils",
|
||||
"substrate-frame-rpc-system",
|
||||
"validator-sets-pallet",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -7879,7 +8017,6 @@ dependencies = [
|
|||
"scale-info",
|
||||
"serde",
|
||||
"sp-core",
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -7913,6 +8050,7 @@ dependencies = [
|
|||
"frame-system",
|
||||
"frame-system-rpc-runtime-api",
|
||||
"hex-literal",
|
||||
"in-instructions-pallet",
|
||||
"pallet-assets",
|
||||
"pallet-balances",
|
||||
"pallet-session",
|
||||
|
@ -7923,7 +8061,6 @@ dependencies = [
|
|||
"scale-info",
|
||||
"serai-primitives",
|
||||
"sp-api",
|
||||
"sp-application-crypto",
|
||||
"sp-block-builder",
|
||||
"sp-core",
|
||||
"sp-inherents",
|
||||
|
@ -9016,6 +9153,79 @@ version = "2.4.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "subxt"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3cbc78fd36035a24883eada29e0205b9b1416172530a7d00a60c07d0337db0c"
|
||||
dependencies = [
|
||||
"bitvec 1.0.1",
|
||||
"derivative",
|
||||
"frame-metadata",
|
||||
"futures",
|
||||
"getrandom 0.2.8",
|
||||
"hex",
|
||||
"jsonrpsee",
|
||||
"parity-scale-codec",
|
||||
"parking_lot 0.12.1",
|
||||
"scale-decode",
|
||||
"scale-info",
|
||||
"scale-value",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sp-core",
|
||||
"sp-runtime",
|
||||
"subxt-macro",
|
||||
"subxt-metadata",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subxt-codegen"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7722c31febf55eb300c73d977da5d65cfd6fb443419b1185b9abcdd9925fd7be"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"frame-metadata",
|
||||
"heck 0.4.0",
|
||||
"hex",
|
||||
"jsonrpsee",
|
||||
"parity-scale-codec",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scale-info",
|
||||
"subxt-metadata",
|
||||
"syn",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subxt-macro"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f64826f2c4ba20e3b2a86ec81a6ae8655ca6b6a4c2a6ccc888b6615efc2df14"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro-error",
|
||||
"subxt-codegen",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subxt-metadata"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "869af75e23513538ad0af046af4a97b8d684e8d202e35ff4127ee061c1110813"
|
||||
dependencies = [
|
||||
"frame-metadata",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"sp-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "svm-rs"
|
||||
version = "0.2.19"
|
||||
|
@ -9798,8 +10008,6 @@ dependencies = [
|
|||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"serde",
|
||||
"sp-core",
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -10789,6 +10997,12 @@ version = "0.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||
|
||||
[[package]]
|
||||
name = "yap"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fc77f52dc9e9b10d55d3f4462c3b7fc393c4f17975d641542833ab2d3bc26ef"
|
||||
|
||||
[[package]]
|
||||
name = "yasna"
|
||||
version = "0.5.1"
|
||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -23,6 +23,11 @@ members = [
|
|||
"processor",
|
||||
|
||||
"substrate/serai/primitives",
|
||||
"substrate/serai/client",
|
||||
|
||||
"substrate/in-instructions/primitives",
|
||||
"substrate/in-instructions/pallet",
|
||||
"substrate/in-instructions/client",
|
||||
|
||||
"substrate/validator-sets/primitives",
|
||||
"substrate/validator-sets/pallet",
|
||||
|
@ -37,7 +42,7 @@ members = [
|
|||
]
|
||||
|
||||
# Always compile Monero (and a variety of dependencies) with optimizations due
|
||||
# to the unoptimized performance of Bulletproofs
|
||||
# to the extensive operations required for Bulletproofs
|
||||
[profile.dev.package]
|
||||
subtle = { opt-level = 3 }
|
||||
curve25519-dalek = { opt-level = 3 }
|
||||
|
@ -55,3 +60,8 @@ monero-serai = { opt-level = 3 }
|
|||
|
||||
[profile.release]
|
||||
panic = "unwind"
|
||||
|
||||
# Required for subxt
|
||||
[patch.crates-io]
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate" }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/substrate" }
|
||||
|
|
|
@ -13,7 +13,6 @@ all-features = true
|
|||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
hex-literal = "0.3"
|
||||
thiserror = "1"
|
||||
rand_core = "0.6"
|
||||
|
||||
|
|
|
@ -25,11 +25,10 @@ zeroize = { version = "1.5", features = ["zeroize_derive"] }
|
|||
subtle = "2.4"
|
||||
|
||||
sha3 = "0.10"
|
||||
blake2 = { version = "0.10", optional = true }
|
||||
|
||||
curve25519-dalek = { version = "3", features = ["std"] }
|
||||
|
||||
group = { version = "0.12" }
|
||||
group = "0.12"
|
||||
dalek-ff-group = { path = "../../crypto/dalek-ff-group", version = "0.1" }
|
||||
multiexp = { path = "../../crypto/multiexp", version = "0.2", features = ["batch"] }
|
||||
|
||||
|
@ -60,4 +59,4 @@ monero-rpc = "0.3"
|
|||
frost = { package = "modular-frost", path = "../../crypto/frost", version = "0.5", features = ["ed25519", "tests"] }
|
||||
|
||||
[features]
|
||||
multisig = ["rand_chacha", "blake2", "transcript", "frost", "dleq"]
|
||||
multisig = ["rand_chacha", "transcript", "frost", "dleq"]
|
||||
|
|
|
@ -63,7 +63,7 @@ fn clsag() {
|
|||
Commitment::new(secrets.1, AMOUNT),
|
||||
Decoys {
|
||||
i: u8::try_from(real).unwrap(),
|
||||
offsets: (1 ..= RING_LEN).into_iter().collect(),
|
||||
offsets: (1 ..= RING_LEN).collect(),
|
||||
ring: ring.clone(),
|
||||
},
|
||||
)
|
||||
|
@ -107,11 +107,7 @@ fn clsag_multisig() {
|
|||
Arc::new(RwLock::new(Some(ClsagDetails::new(
|
||||
ClsagInput::new(
|
||||
Commitment::new(randomness, AMOUNT),
|
||||
Decoys {
|
||||
i: RING_INDEX,
|
||||
offsets: (1 ..= RING_LEN).into_iter().collect(),
|
||||
ring: ring.clone(),
|
||||
},
|
||||
Decoys { i: RING_INDEX, offsets: (1 ..= RING_LEN).collect(), ring: ring.clone() },
|
||||
)
|
||||
.unwrap(),
|
||||
mask_sum,
|
||||
|
|
|
@ -248,7 +248,7 @@ impl SignMachine<Transaction> for TransactionSignMachine {
|
|||
// Find out who's included
|
||||
// This may not be a valid set of signers yet the algorithm machine will error if it's not
|
||||
commitments.remove(&self.i); // Remove, if it was included for some reason
|
||||
let mut included = commitments.keys().into_iter().cloned().collect::<Vec<_>>();
|
||||
let mut included = commitments.keys().cloned().collect::<Vec<_>>();
|
||||
included.push(self.i);
|
||||
included.sort_unstable();
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ pub async fn mine_until_unlocked(rpc: &Rpc, addr: &str, tx_hash: [u8; 32]) {
|
|||
}
|
||||
|
||||
// Mines 60 blocks and returns an unlocked miner TX output.
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_miner_tx_output(rpc: &Rpc, view: &ViewPair) -> SpendableOutput {
|
||||
let mut scanner = Scanner::from_view(view.clone(), Some(HashSet::new()));
|
||||
|
||||
|
|
|
@ -48,6 +48,9 @@ exceptions = [
|
|||
|
||||
{ allow = ["AGPL-3.0"], name = "serai-processor" },
|
||||
|
||||
{ allow = ["AGPL-3.0"], name = "in-instructions-pallet" },
|
||||
{ allow = ["AGPL-3.0"], name = "in-instructions-client" },
|
||||
|
||||
{ allow = ["AGPL-3.0"], name = "validator-sets-pallet" },
|
||||
|
||||
{ allow = ["AGPL-3.0"], name = "sp-tendermint" },
|
||||
|
@ -56,6 +59,8 @@ exceptions = [
|
|||
|
||||
{ allow = ["AGPL-3.0"], name = "serai-runtime" },
|
||||
{ allow = ["AGPL-3.0"], name = "serai-node" },
|
||||
|
||||
{ allow = ["AGPL-3.0"], name = "serai-client" },
|
||||
]
|
||||
|
||||
[[licenses.clarify]]
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# Serai
|
||||
|
||||
Serai is a decentralized execution layer whose validators form multisig wallets
|
||||
for various connected networks, offering secure decentralized custody of foreign
|
||||
assets to applications built on it.
|
||||
for various connected networks, offering secure decentralized control of foreign
|
||||
coins to applications built on it.
|
||||
|
||||
Serai is exemplified by Serai DEX, an automated-market-maker (AMM) decentralized
|
||||
exchange, allowing swapping Bitcoin, Ether, DAI, and Monero. It is the premier
|
||||
|
|
|
@ -9,11 +9,11 @@ Ethereum addresses are 20-byte hashes.
|
|||
Ethereum In Instructions are present via being appended to the calldata
|
||||
transferring funds to Serai. `origin` is automatically set to the party from
|
||||
which funds are being transferred. For an ERC20, this is `from`. For ETH, this
|
||||
is the caller. `data` is limited to 255 bytes.
|
||||
is the caller.
|
||||
|
||||
### Out Instructions
|
||||
|
||||
`data` is limited to 255 bytes.
|
||||
`data` is limited to 512 bytes.
|
||||
|
||||
If `data` is provided, the Ethereum Router will call a contract-calling child
|
||||
contract in order to sandbox it. The first byte of `data` designates which child
|
||||
|
|
|
@ -3,90 +3,94 @@
|
|||
Instructions are used to communicate with networks connected to Serai, and they
|
||||
come in two forms:
|
||||
|
||||
- In Instructions are [Application Calls](../Serai.md#application-calls),
|
||||
paired with incoming funds. Encoded in transactions on connected networks,
|
||||
Serai will parse out instructions when it receives funds, executing the included
|
||||
calls.
|
||||
- In Instructions are programmable specifications paired with incoming coins,
|
||||
encoded into transactions on connected networks. Serai will parse included
|
||||
instructions when it receives coins, executing the included specs.
|
||||
|
||||
- Out Instructions detail how to transfer assets, either to a Serai address or
|
||||
an address native to the asset in question.
|
||||
- Out Instructions detail how to transfer coins, either to a Serai address or
|
||||
an address native to the coin in question.
|
||||
|
||||
A transaction containing an In Instruction and an Out Instruction (to a native
|
||||
address) will receive funds to Serai and send funds from Serai, without
|
||||
address) will receive coins to Serai and send coins from Serai, without
|
||||
requiring directly performing any transactions on Serai itself.
|
||||
|
||||
All instructions are encoded under [Shorthand](#shorthand). Shorthand provides
|
||||
frequent use cases to create minimal data representations on connected networks.
|
||||
|
||||
Instructions are interpreted according to their non-Serai network. Addresses
|
||||
have no validation performed, beyond being a valid enum entry (when applicable)
|
||||
of the correct length, unless otherwise noted. If the processor is instructed to
|
||||
act on invalid data, or send to itself, it will drop the entire instruction.
|
||||
have no validation performed unless otherwise noted. If the processor is
|
||||
instructed to act on invalid data, it will drop the entire instruction.
|
||||
|
||||
### Serialization
|
||||
|
||||
- Numbers are exclusively unsigned and encoded as compact integers under
|
||||
SCALE.
|
||||
- Enums are prefixed by an ordinal byte of their type, followed by their
|
||||
actual values.
|
||||
- Vectors are prefixed by their length.
|
||||
- In Instruction fields are numbered and sequentially encoded, allowing
|
||||
omission, each prefixed by an ordinal byte. This is due to its fields being more
|
||||
frequently omitted than not, making their presence what's notable.
|
||||
- All other types have their fields sequentially encoded with no markers.
|
||||
Instructions are SCALE encoded.
|
||||
|
||||
Certain fields may be omitted depending on the network in question.
|
||||
### Application Call
|
||||
|
||||
### In Instructions
|
||||
- `application` (u16): The application of Serai to call. Currently, only 0,
|
||||
Serai DEX is valid.
|
||||
- `data` (Data): The data to call the application with.
|
||||
|
||||
- `origin` (Address): Address from the network of origin which sent funds in.
|
||||
- `target` (Address): The ink! contract to transfer the incoming funds to.
|
||||
- `data` (Vec\<u8>): The data to call `target` with.
|
||||
### In Instruction
|
||||
|
||||
InInstruction is an enum of SeraiAddress and ApplicationCall.
|
||||
|
||||
The specified target will be minted an appropriate amount of the respective
|
||||
Serai token. If an Application Call, the encoded call will be executed.
|
||||
|
||||
### Refundable In Instruction
|
||||
|
||||
- `origin` (Option\<ExternalAddress>): Address, from the network of origin,
|
||||
which sent coins in.
|
||||
- `instruction` (InInstruction): The action to perform with the incoming
|
||||
coins.
|
||||
|
||||
Networks may automatically provide `origin`. If they do, the instruction may
|
||||
still provide `origin`, overriding the automatically provided value. If no
|
||||
`origin` is provided, the instruction is dropped.
|
||||
still provide `origin`, overriding the automatically provided value.
|
||||
|
||||
Upon receiving funds, the respective Serai Asset contract is called, minting the
|
||||
appropriate amount of coins, and transferring them to `target`, calling it with
|
||||
the attached data.
|
||||
If the instruction fails, coins are scheduled to be returned to `origin`,
|
||||
if provided.
|
||||
|
||||
If the instruction fails, funds are scheduled to be returned to `origin`.
|
||||
### Destination
|
||||
|
||||
### Out Instructions
|
||||
Destination is an enum of SeraiAddress and ExternalAddress.
|
||||
|
||||
- `destination` (Enum { Native(Address), Serai(Address) }): Address to receive
|
||||
funds to.
|
||||
- `data` (Option\<Vec\<u8>>): The data to call
|
||||
the target with.
|
||||
### Out Instruction
|
||||
|
||||
Transfer the funds included with this instruction to the specified address with
|
||||
the specified data. Asset contracts perform no validation on native
|
||||
addresses/data.
|
||||
- `destination` (Destination): Address to receive coins to.
|
||||
- `data` (Option\<Data>): The data to call the destination with.
|
||||
|
||||
Transfer the coins included with this instruction to the specified address with
|
||||
the specified data. No validation of external addresses/data is performed
|
||||
on-chain. If data is specified for a chain not supporting data, it is silently
|
||||
dropped.
|
||||
|
||||
### Shorthand
|
||||
|
||||
Shorthand is an enum which expands to an In Instruction.
|
||||
Shorthand is an enum which expands to an Refundable In Instruction.
|
||||
|
||||
##### Raw
|
||||
|
||||
Raw Shorthand encodes a raw In Instruction with no further processing. This is
|
||||
a verbose fallback option for infrequent use cases not covered by Shorthand.
|
||||
Raw Shorthand encodes a raw Refundable In Instruction in a Data, with no further
|
||||
processing. This is a verbose fallback option for infrequent use cases not
|
||||
covered by Shorthand.
|
||||
|
||||
##### Swap
|
||||
|
||||
- `origin` (Option\<Address>): In Instruction's `origin`.
|
||||
- `coin` (Coin): Coin to swap funds for.
|
||||
- `minimum` (Amount): Minimum amount of `coin` to receive.
|
||||
- `out` (Out Instruction): Final destination for funds.
|
||||
- `origin` (Option\<ExternalAddress>): Refundable In Instruction's `origin`.
|
||||
- `coin` (Coin): Coin to swap funds for.
|
||||
- `minimum` (Amount): Minimum amount of `coin` to receive.
|
||||
- `out` (Out Instruction): Final destination for funds.
|
||||
|
||||
which expands to:
|
||||
|
||||
```
|
||||
In Instruction {
|
||||
RefundableInInstruction {
|
||||
origin,
|
||||
target: Router,
|
||||
data: swap(Incoming Asset, out, minimum)
|
||||
instruction: ApplicationCall {
|
||||
application: DEX,
|
||||
data: swap(Incoming Asset, coin, minimum, out)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -99,19 +103,23 @@ where `swap` is a function which:
|
|||
|
||||
##### Add Liquidity
|
||||
|
||||
- `origin` (Option\<Address>): In Instruction's `origin`.
|
||||
- `minimum` (Amount): Minimum amount of SRI to receive.
|
||||
- `gas` (Amount): Amount of SRI to send to `address` to cover
|
||||
gas in the future.
|
||||
- `address` (Address): Account to send the created liquidity tokens.
|
||||
- `origin` (Option\<ExternalAddress>): Refundable In Instruction's `origin`.
|
||||
- `minimum` (Amount): Minimum amount of SRI tokens to swap
|
||||
half for.
|
||||
- `gas` (Amount): Amount of SRI to send to `address` to
|
||||
cover gas in the future.
|
||||
- `address` (Address): Account to send the created liquidity
|
||||
tokens.
|
||||
|
||||
which expands to:
|
||||
|
||||
```
|
||||
In Instruction {
|
||||
RefundableInInstruction {
|
||||
origin,
|
||||
target: Router,
|
||||
data: swap_and_add_liquidity(Incoming Asset, address, minimum, gas)
|
||||
instruction: ApplicationCall {
|
||||
application: DEX,
|
||||
data: swap_and_add_liquidity(Incoming Asset, minimum, gas, address)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -120,5 +128,5 @@ where `swap_and_add_liquidity` is a function which:
|
|||
1) Swaps half of the incoming funds for SRI.
|
||||
2) Checks the amount of SRI received is greater than `minimum`.
|
||||
3) Calls `swap_and_add_liquidity` with the amount of SRI received - `gas`, and
|
||||
a matching amount of the incoming asset.
|
||||
a matching amount of the incoming coin.
|
||||
4) Transfers any leftover funds to `address`.
|
||||
|
|
|
@ -5,14 +5,17 @@
|
|||
These are the list of types used to represent various properties within the
|
||||
protocol.
|
||||
|
||||
| Alias | Type |
|
||||
|------------------------|--------------------------------|
|
||||
| Amount | u64 |
|
||||
| Coin | u32 |
|
||||
| Session | u32 |
|
||||
| Validator Set Index | u16 |
|
||||
| Validator Set Instance | (Session, Validator Set Index) |
|
||||
| Key | Vec\<u8> |
|
||||
| Alias | Type |
|
||||
|------------------------|----------------------------------------------|
|
||||
| SeraiAddress | sr25519::Public (unchecked [u8; 32] wrapper) |
|
||||
| Amount | u64 |
|
||||
| Coin | u32 |
|
||||
| Session | u32 |
|
||||
| Validator Set Index | u16 |
|
||||
| Validator Set Instance | (Session, Validator Set Index) |
|
||||
| Key | BoundedVec\<u8, 96> |
|
||||
| ExternalAddress | BoundedVec\<u8, 74> |
|
||||
| Data | BoundedVec\<u8, 512> |
|
||||
|
||||
### Networks
|
||||
|
||||
|
@ -36,7 +39,8 @@ Coins exist over a network and have a distinct integer ID.
|
|||
|
||||
| Coin | Network | ID |
|
||||
|----------|----------|----|
|
||||
| Bitcoin | Bitcoin | 0 |
|
||||
| Ether | Ethereum | 1 |
|
||||
| DAI | Ethereum | 2 |
|
||||
| Monero | Monero | 3 |
|
||||
| Serai | Serai | 0 |
|
||||
| Bitcoin | Bitcoin | 1 |
|
||||
| Ether | Ethereum | 2 |
|
||||
| DAI | Ethereum | 3 |
|
||||
| Monero | Monero | 4 |
|
||||
|
|
|
@ -14,26 +14,28 @@ all-features = true
|
|||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
# Macros
|
||||
async-trait = "0.1"
|
||||
zeroize = "1.5"
|
||||
thiserror = "1"
|
||||
rand_core = "0.6"
|
||||
|
||||
# Cryptography
|
||||
group = "0.12"
|
||||
|
||||
curve25519-dalek = { version = "3", features = ["std"] }
|
||||
|
||||
transcript = { package = "flexible-transcript", path = "../crypto/transcript", features = ["recommended"] }
|
||||
dalek-ff-group = { path = "../crypto/dalek-ff-group" }
|
||||
|
||||
transcript = { package = "flexible-transcript", path = "../crypto/transcript" }
|
||||
frost = { package = "modular-frost", path = "../crypto/frost", features = ["ed25519"] }
|
||||
|
||||
# Monero
|
||||
monero-serai = { path = "../coins/monero", features = ["multisig"] }
|
||||
|
||||
[dev-dependencies]
|
||||
rand_core = "0.6"
|
||||
|
||||
hex = "0.4"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
futures = "0.3"
|
||||
|
|
|
@ -1,113 +1,4 @@
|
|||
use std::{
|
||||
sync::{Arc, RwLock},
|
||||
collections::HashMap,
|
||||
};
|
||||
mod send;
|
||||
pub(crate) use send::test_send;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use rand_core::OsRng;
|
||||
|
||||
use crate::{
|
||||
NetworkError, Network,
|
||||
coin::{Coin, Monero},
|
||||
wallet::{WalletKeys, MemCoinDb, Wallet},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct LocalNetwork {
|
||||
i: u16,
|
||||
size: u16,
|
||||
round: usize,
|
||||
#[allow(clippy::type_complexity)]
|
||||
rounds: Arc<RwLock<Vec<HashMap<u16, Vec<u8>>>>>,
|
||||
}
|
||||
|
||||
impl LocalNetwork {
|
||||
fn new(size: u16) -> Vec<LocalNetwork> {
|
||||
let rounds = Arc::new(RwLock::new(vec![]));
|
||||
let mut res = vec![];
|
||||
for i in 1 ..= size {
|
||||
res.push(LocalNetwork { i, size, round: 0, rounds: rounds.clone() });
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Network for LocalNetwork {
|
||||
async fn round(&mut self, data: Vec<u8>) -> Result<HashMap<u16, Vec<u8>>, NetworkError> {
|
||||
{
|
||||
let mut rounds = self.rounds.write().unwrap();
|
||||
if rounds.len() == self.round {
|
||||
rounds.push(HashMap::new());
|
||||
}
|
||||
rounds[self.round].insert(self.i, data);
|
||||
}
|
||||
|
||||
while {
|
||||
let read = self.rounds.try_read().unwrap();
|
||||
read[self.round].len() != usize::from(self.size)
|
||||
} {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
let mut res = self.rounds.try_read().unwrap()[self.round].clone();
|
||||
res.remove(&self.i);
|
||||
self.round += 1;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_send<C: Coin + Clone>(coin: C, fee: C::Fee) {
|
||||
// Mine blocks so there's a confirmed block
|
||||
coin.mine_block().await;
|
||||
let latest = coin.get_latest_block_number().await.unwrap();
|
||||
|
||||
let mut keys = frost::tests::key_gen::<_, C::Curve>(&mut OsRng);
|
||||
let threshold = keys[&1].params().t();
|
||||
let mut networks = LocalNetwork::new(threshold);
|
||||
|
||||
let mut wallets = vec![];
|
||||
for i in 1 ..= threshold {
|
||||
let mut wallet = Wallet::new(MemCoinDb::new(), coin.clone());
|
||||
wallet.acknowledge_block(0, latest);
|
||||
wallet.add_keys(&WalletKeys::new(keys.remove(&i).unwrap(), 0));
|
||||
wallets.push(wallet);
|
||||
}
|
||||
|
||||
// Get the chain to a length where blocks have sufficient confirmations
|
||||
while (latest + (C::CONFIRMATIONS - 1)) > coin.get_latest_block_number().await.unwrap() {
|
||||
coin.mine_block().await;
|
||||
}
|
||||
|
||||
for wallet in wallets.iter_mut() {
|
||||
// Poll to activate the keys
|
||||
wallet.poll().await.unwrap();
|
||||
}
|
||||
|
||||
coin.test_send(wallets[0].address()).await;
|
||||
|
||||
let mut futures = vec![];
|
||||
for (network, wallet) in networks.iter_mut().zip(wallets.iter_mut()) {
|
||||
wallet.poll().await.unwrap();
|
||||
|
||||
let latest = coin.get_latest_block_number().await.unwrap();
|
||||
wallet.acknowledge_block(1, latest - (C::CONFIRMATIONS - 1));
|
||||
let signable = wallet
|
||||
.prepare_sends(1, vec![(wallet.address(), 10000000000)], fee)
|
||||
.await
|
||||
.unwrap()
|
||||
.1
|
||||
.swap_remove(0);
|
||||
futures.push(wallet.attempt_send(network, signable));
|
||||
}
|
||||
|
||||
println!("{:?}", hex::encode(futures::future::join_all(futures).await.swap_remove(0).unwrap().0));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn monero() {
|
||||
let monero = Monero::new("http://127.0.0.1:18081".to_string()).await;
|
||||
let fee = monero.get_fee().await;
|
||||
test_send(monero, fee).await;
|
||||
}
|
||||
mod monero;
|
||||
|
|
11
processor/src/tests/monero.rs
Normal file
11
processor/src/tests/monero.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use crate::{
|
||||
coin::{Coin, Monero},
|
||||
tests::test_send,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn monero() {
|
||||
let monero = Monero::new("http://127.0.0.1:18081".to_string()).await;
|
||||
let fee = monero.get_fee().await;
|
||||
test_send(monero, fee).await;
|
||||
}
|
106
processor/src/tests/send.rs
Normal file
106
processor/src/tests/send.rs
Normal file
|
@ -0,0 +1,106 @@
|
|||
use std::{
|
||||
sync::{Arc, RwLock},
|
||||
collections::HashMap,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
use rand_core::OsRng;
|
||||
|
||||
use crate::{
|
||||
NetworkError, Network,
|
||||
coin::Coin,
|
||||
wallet::{WalletKeys, MemCoinDb, Wallet},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct LocalNetwork {
|
||||
i: u16,
|
||||
size: u16,
|
||||
round: usize,
|
||||
#[allow(clippy::type_complexity)]
|
||||
rounds: Arc<RwLock<Vec<HashMap<u16, Vec<u8>>>>>,
|
||||
}
|
||||
|
||||
impl LocalNetwork {
|
||||
fn new(size: u16) -> Vec<LocalNetwork> {
|
||||
let rounds = Arc::new(RwLock::new(vec![]));
|
||||
let mut res = vec![];
|
||||
for i in 1 ..= size {
|
||||
res.push(LocalNetwork { i, size, round: 0, rounds: rounds.clone() });
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Network for LocalNetwork {
|
||||
async fn round(&mut self, data: Vec<u8>) -> Result<HashMap<u16, Vec<u8>>, NetworkError> {
|
||||
{
|
||||
let mut rounds = self.rounds.write().unwrap();
|
||||
if rounds.len() == self.round {
|
||||
rounds.push(HashMap::new());
|
||||
}
|
||||
rounds[self.round].insert(self.i, data);
|
||||
}
|
||||
|
||||
while {
|
||||
let read = self.rounds.try_read().unwrap();
|
||||
read[self.round].len() != usize::from(self.size)
|
||||
} {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
let mut res = self.rounds.try_read().unwrap()[self.round].clone();
|
||||
res.remove(&self.i);
|
||||
self.round += 1;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn test_send<C: Coin + Clone>(coin: C, fee: C::Fee) {
|
||||
// Mine blocks so there's a confirmed block
|
||||
coin.mine_block().await;
|
||||
let latest = coin.get_latest_block_number().await.unwrap();
|
||||
|
||||
let mut keys = frost::tests::key_gen::<_, C::Curve>(&mut OsRng);
|
||||
let threshold = keys[&1].params().t();
|
||||
let mut networks = LocalNetwork::new(threshold);
|
||||
|
||||
let mut wallets = vec![];
|
||||
for i in 1 ..= threshold {
|
||||
let mut wallet = Wallet::new(MemCoinDb::new(), coin.clone());
|
||||
wallet.acknowledge_block(0, latest);
|
||||
wallet.add_keys(&WalletKeys::new(keys.remove(&i).unwrap(), 0));
|
||||
wallets.push(wallet);
|
||||
}
|
||||
|
||||
// Get the chain to a length where blocks have sufficient confirmations
|
||||
while (latest + (C::CONFIRMATIONS - 1)) > coin.get_latest_block_number().await.unwrap() {
|
||||
coin.mine_block().await;
|
||||
}
|
||||
|
||||
for wallet in wallets.iter_mut() {
|
||||
// Poll to activate the keys
|
||||
wallet.poll().await.unwrap();
|
||||
}
|
||||
|
||||
coin.test_send(wallets[0].address()).await;
|
||||
|
||||
let mut futures = vec![];
|
||||
for (network, wallet) in networks.iter_mut().zip(wallets.iter_mut()) {
|
||||
wallet.poll().await.unwrap();
|
||||
|
||||
let latest = coin.get_latest_block_number().await.unwrap();
|
||||
wallet.acknowledge_block(1, latest - (C::CONFIRMATIONS - 1));
|
||||
let signable = wallet
|
||||
.prepare_sends(1, vec![(wallet.address(), 10000000000)], fee)
|
||||
.await
|
||||
.unwrap()
|
||||
.1
|
||||
.swap_remove(0);
|
||||
futures.push(wallet.attempt_send(network, signable));
|
||||
}
|
||||
|
||||
println!("{:?}", hex::encode(futures::future::join_all(futures).await.swap_remove(0).unwrap().0));
|
||||
}
|
24
substrate/in-instructions/client/Cargo.toml
Normal file
24
substrate/in-instructions/client/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "in-instructions-client"
|
||||
version = "0.1.0"
|
||||
description = "Package In Instructions into inherent transactions"
|
||||
license = "AGPL-3.0-only"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1"
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3", features = ["derive", "max-encoded-len"] }
|
||||
|
||||
jsonrpsee-core = "0.16"
|
||||
jsonrpsee-http-client = "0.16"
|
||||
|
||||
sp-inherents = { git = "https://github.com/serai-dex/substrate" }
|
||||
|
||||
in-instructions-pallet = { path = "../pallet" }
|
15
substrate/in-instructions/client/LICENSE
Normal file
15
substrate/in-instructions/client/LICENSE
Normal file
|
@ -0,0 +1,15 @@
|
|||
AGPL-3.0-only license
|
||||
|
||||
Copyright (c) 2022-2023 Luke Parker
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License Version 3 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
47
substrate/in-instructions/client/src/lib.rs
Normal file
47
substrate/in-instructions/client/src/lib.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
||||
use scale::Decode;
|
||||
|
||||
use jsonrpsee_core::client::ClientT;
|
||||
use jsonrpsee_http_client::HttpClientBuilder;
|
||||
|
||||
use sp_inherents::{Error, InherentData, InherentIdentifier};
|
||||
|
||||
use in_instructions_pallet::{INHERENT_IDENTIFIER, Updates, InherentError};
|
||||
|
||||
pub struct InherentDataProvider;
|
||||
impl InherentDataProvider {
|
||||
#[allow(clippy::new_without_default)] // This isn't planned to forever have empty arguments
|
||||
pub fn new() -> InherentDataProvider {
|
||||
InherentDataProvider
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl sp_inherents::InherentDataProvider for InherentDataProvider {
|
||||
async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> {
|
||||
let updates: Updates = (|| async {
|
||||
let client = HttpClientBuilder::default().build("http://127.0.0.1:5134")?;
|
||||
client.request("processor_coinUpdates", Vec::<u8>::new()).await
|
||||
})()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::Application(Box::from(format!("couldn't communicate with processor: {e}")))
|
||||
})?;
|
||||
inherent_data.put_data(INHERENT_IDENTIFIER, &updates)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn try_handle_error(
|
||||
&self,
|
||||
identifier: &InherentIdentifier,
|
||||
mut error: &[u8],
|
||||
) -> Option<Result<(), Error>> {
|
||||
if *identifier != INHERENT_IDENTIFIER {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Err(Error::Application(Box::from(<InherentError as Decode>::decode(&mut error).ok()?))))
|
||||
}
|
||||
}
|
51
substrate/in-instructions/pallet/Cargo.toml
Normal file
51
substrate/in-instructions/pallet/Cargo.toml
Normal file
|
@ -0,0 +1,51 @@
|
|||
[package]
|
||||
name = "in-instructions-pallet"
|
||||
version = "0.1.0"
|
||||
description = "Execute calls via In Instructions from inherent transactions"
|
||||
license = "AGPL-3.0-only"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
thiserror = { version = "1", optional = true }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "max-encoded-len"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
serde = { version = "1", optional = true }
|
||||
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-inherents = { 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/substrate", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../../serai/primitives", default-features = false }
|
||||
in-instructions-primitives = { path = "../primitives", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"thiserror",
|
||||
|
||||
"scale/std",
|
||||
"scale-info/std",
|
||||
|
||||
"serde",
|
||||
|
||||
"sp-std/std",
|
||||
"sp-inherents/std",
|
||||
"sp-runtime/std",
|
||||
|
||||
"frame-system/std",
|
||||
"frame-support/std",
|
||||
|
||||
"serai-primitives/std",
|
||||
"in-instructions-primitives/std",
|
||||
]
|
||||
default = ["std"]
|
15
substrate/in-instructions/pallet/LICENSE
Normal file
15
substrate/in-instructions/pallet/LICENSE
Normal file
|
@ -0,0 +1,15 @@
|
|||
AGPL-3.0-only license
|
||||
|
||||
Copyright (c) 2022-2023 Luke Parker
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License Version 3 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
240
substrate/in-instructions/pallet/src/lib.rs
Normal file
240
substrate/in-instructions/pallet/src/lib.rs
Normal file
|
@ -0,0 +1,240 @@
|
|||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use scale::{Encode, Decode};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use sp_std::vec::Vec;
|
||||
use sp_inherents::{InherentIdentifier, IsFatalError};
|
||||
|
||||
use sp_runtime::RuntimeDebug;
|
||||
|
||||
use serai_primitives::{BlockNumber, BlockHash, Coin};
|
||||
|
||||
pub use in_instructions_primitives as primitives;
|
||||
use primitives::InInstruction;
|
||||
|
||||
pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"ininstrs";
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, TypeInfo, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct Batch {
|
||||
pub id: BlockHash,
|
||||
pub instructions: Vec<InInstruction>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, TypeInfo, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct Update {
|
||||
// Coin's latest block number
|
||||
pub block_number: BlockNumber,
|
||||
pub batches: Vec<Batch>,
|
||||
}
|
||||
|
||||
// None if the current block producer isn't operating over this coin or otherwise failed to get
|
||||
// data
|
||||
pub type Updates = Vec<Option<Update>>;
|
||||
|
||||
#[derive(Clone, Copy, Encode, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(Decode, thiserror::Error))]
|
||||
pub enum InherentError {
|
||||
#[cfg_attr(feature = "std", error("invalid call"))]
|
||||
InvalidCall,
|
||||
#[cfg_attr(feature = "std", error("inherent has {0} updates despite us having {1} coins"))]
|
||||
InvalidUpdateQuantity(u32, u32),
|
||||
#[cfg_attr(
|
||||
feature = "std",
|
||||
error("inherent for coin {0:?} has block number {1:?} despite us having {2:?}")
|
||||
)]
|
||||
UnrecognizedBlockNumber(Coin, BlockNumber, BlockNumber),
|
||||
#[cfg_attr(
|
||||
feature = "std",
|
||||
error("inherent for coin {0:?} has block number {1:?} which doesn't succeed {2:?}")
|
||||
)]
|
||||
InvalidBlockNumber(Coin, BlockNumber, BlockNumber),
|
||||
#[cfg_attr(feature = "std", error("coin {0:?} has {1} more batches than we do"))]
|
||||
UnrecognizedBatches(Coin, u32),
|
||||
#[cfg_attr(feature = "std", error("coin {0:?} has a different batch (ID {1:?})"))]
|
||||
DifferentBatch(Coin, BlockHash),
|
||||
}
|
||||
|
||||
impl IsFatalError for InherentError {
|
||||
fn is_fatal_error(&self) -> bool {
|
||||
match self {
|
||||
InherentError::InvalidCall | InherentError::InvalidUpdateQuantity(..) => true,
|
||||
InherentError::UnrecognizedBlockNumber(..) => false,
|
||||
InherentError::InvalidBlockNumber(..) => true,
|
||||
InherentError::UnrecognizedBatches(..) => false,
|
||||
// One of our nodes is definitively wrong. If it's ours (signified by it passing consensus),
|
||||
// we should panic. If it's theirs, they should be slashed
|
||||
// Unfortunately, we can't return fatal here to trigger a slash as fatal should only be used
|
||||
// for undeniable, technical invalidity
|
||||
// TODO: Code a way in which this still triggers a slash vote
|
||||
InherentError::DifferentBatch(..) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn coin_from_index(index: usize) -> Coin {
|
||||
// Offset by 1 since Serai is the first coin, yet Serai doesn't have updates
|
||||
Coin::from(1 + u32::try_from(index).unwrap())
|
||||
}
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config<BlockNumber = u32> {
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
Batch { coin: Coin, id: BlockHash },
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::generate_store(pub(crate) trait Store)]
|
||||
pub struct Pallet<T>(PhantomData<T>);
|
||||
|
||||
// Used to only allow one set of updates per block, preventing double updating
|
||||
#[pallet::storage]
|
||||
pub(crate) type Once<T: Config> = StorageValue<_, bool, ValueQuery>;
|
||||
// Latest block number agreed upon for a coin
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn block_number)]
|
||||
pub(crate) type BlockNumbers<T: Config> =
|
||||
StorageMap<_, Blake2_256, Coin, BlockNumber, ValueQuery>;
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn on_finalize(_: BlockNumberFor<T>) {
|
||||
Once::<T>::take();
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight((0, DispatchClass::Mandatory))] // TODO
|
||||
pub fn execute(origin: OriginFor<T>, updates: Updates) -> DispatchResult {
|
||||
ensure_none(origin)?;
|
||||
assert!(!Once::<T>::exists());
|
||||
Once::<T>::put(true);
|
||||
|
||||
for (coin, update) in updates.iter().enumerate() {
|
||||
if let Some(update) = update {
|
||||
let coin = coin_from_index(coin);
|
||||
BlockNumbers::<T>::insert(coin, update.block_number);
|
||||
|
||||
for batch in &update.batches {
|
||||
// TODO: EXECUTE
|
||||
Self::deposit_event(Event::Batch { coin, id: batch.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::inherent]
|
||||
impl<T: Config> ProvideInherent for Pallet<T> {
|
||||
type Call = Call<T>;
|
||||
type Error = InherentError;
|
||||
const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
|
||||
|
||||
fn create_inherent(data: &InherentData) -> Option<Self::Call> {
|
||||
data
|
||||
.get_data::<Updates>(&INHERENT_IDENTIFIER)
|
||||
.unwrap()
|
||||
.map(|updates| Call::execute { updates })
|
||||
}
|
||||
|
||||
// Assumes that only not yet handled batches are provided as inherent data
|
||||
fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
|
||||
// First unwrap is for the Result of fetching/decoding the Updates
|
||||
// Second unwrap is for the Option of if they exist
|
||||
let expected = data.get_data::<Updates>(&INHERENT_IDENTIFIER).unwrap().unwrap();
|
||||
// Match to be exhaustive
|
||||
let updates = match call {
|
||||
Call::execute { ref updates } => updates,
|
||||
_ => Err(InherentError::InvalidCall)?,
|
||||
};
|
||||
|
||||
// The block producer should've provided one update per coin
|
||||
// We, an honest node, did provide one update per coin
|
||||
// Accordingly, we should have the same amount of updates
|
||||
if updates.len() != expected.len() {
|
||||
Err(InherentError::InvalidUpdateQuantity(
|
||||
updates.len().try_into().unwrap(),
|
||||
expected.len().try_into().unwrap(),
|
||||
))?;
|
||||
}
|
||||
|
||||
// This zip is safe since we verified they're equally sized
|
||||
// This should be written as coins.zip(updates.iter().zip(&expected)), where coins is the
|
||||
// validator set's coins
|
||||
// That'd require having context on the validator set right now which isn't worth pulling in
|
||||
// right now, when we only have one validator set
|
||||
for (coin, both) in updates.iter().zip(&expected).enumerate() {
|
||||
let coin = coin_from_index(coin);
|
||||
match both {
|
||||
// Block producer claims there's an update for this coin, as do we
|
||||
(Some(update), Some(expected)) => {
|
||||
if update.block_number.0 > expected.block_number.0 {
|
||||
Err(InherentError::UnrecognizedBlockNumber(
|
||||
coin,
|
||||
update.block_number,
|
||||
expected.block_number,
|
||||
))?;
|
||||
}
|
||||
|
||||
let prev = BlockNumbers::<T>::get(coin);
|
||||
if update.block_number.0 <= prev.0 {
|
||||
Err(InherentError::InvalidBlockNumber(coin, update.block_number, prev))?;
|
||||
}
|
||||
|
||||
if update.batches.len() > expected.batches.len() {
|
||||
Err(InherentError::UnrecognizedBatches(
|
||||
coin,
|
||||
(update.batches.len() - expected.batches.len()).try_into().unwrap(),
|
||||
))?;
|
||||
}
|
||||
|
||||
for (batch, expected) in update.batches.iter().zip(&expected.batches) {
|
||||
if batch != expected {
|
||||
Err(InherentError::DifferentBatch(coin, batch.id))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Block producer claims there's an update for this coin, yet we don't
|
||||
(Some(update), None) => {
|
||||
Err(InherentError::UnrecognizedBatches(coin, update.batches.len().try_into().unwrap()))?
|
||||
}
|
||||
|
||||
// Block producer didn't include update for this coin
|
||||
(None, _) => (),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_inherent(_: &Self::Call) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub use pallet::*;
|
25
substrate/in-instructions/primitives/Cargo.toml
Normal file
25
substrate/in-instructions/primitives/Cargo.toml
Normal file
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "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"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../../serai/primitives", default-features = false }
|
||||
|
||||
[features]
|
||||
std = ["scale/std", "scale-info/std", "serde", "sp-core/std", "serai-primitives/std"]
|
||||
default = ["std"]
|
21
substrate/in-instructions/primitives/LICENSE
Normal file
21
substrate/in-instructions/primitives/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
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.
|
38
substrate/in-instructions/primitives/src/incoming.rs
Normal file
38
substrate/in-instructions/primitives/src/incoming.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
use scale::{Encode, Decode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use sp_core::{ConstU32, bounded::BoundedVec};
|
||||
|
||||
use serai_primitives::SeraiAddress;
|
||||
|
||||
use crate::{MAX_DATA_LEN, ExternalAddress};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub enum Application {
|
||||
DEX,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct ApplicationCall {
|
||||
application: Application,
|
||||
data: BoundedVec<u8, ConstU32<{ MAX_DATA_LEN }>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub enum InInstruction {
|
||||
Transfer(SeraiAddress),
|
||||
Call(ApplicationCall),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct RefundableInInstruction {
|
||||
pub origin: Option<ExternalAddress>,
|
||||
pub instruction: InInstruction,
|
||||
}
|
47
substrate/in-instructions/primitives/src/lib.rs
Normal file
47
substrate/in-instructions/primitives/src/lib.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use scale::{Encode, Decode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use sp_core::{ConstU32, bounded::BoundedVec};
|
||||
|
||||
// Monero, our current longest address candidate, has a longest address of featured with payment ID
|
||||
// 1 (enum) + 1 (flags) + 64 (two keys) + 8 (payment ID) = 74
|
||||
pub const MAX_ADDRESS_LEN: u32 = 74;
|
||||
// Should be enough for a Uniswap v3 call
|
||||
pub const MAX_DATA_LEN: u32 = 512;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct ExternalAddress(BoundedVec<u8, ConstU32<{ MAX_ADDRESS_LEN }>>);
|
||||
impl ExternalAddress {
|
||||
#[cfg(feature = "std")]
|
||||
pub fn new(address: Vec<u8>) -> Result<ExternalAddress, &'static str> {
|
||||
Ok(ExternalAddress(address.try_into().map_err(|_| "address length exceeds {MAX_ADDRESS_LEN}")?))
|
||||
}
|
||||
|
||||
pub fn address(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub fn consume(self) -> Vec<u8> {
|
||||
self.0.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
// Not "in" as "in" is a keyword
|
||||
mod incoming;
|
||||
pub use incoming::*;
|
||||
|
||||
// Not "out" to match in
|
||||
mod outgoing;
|
||||
pub use outgoing::*;
|
||||
|
||||
mod shorthand;
|
||||
pub use shorthand::*;
|
25
substrate/in-instructions/primitives/src/outgoing.rs
Normal file
25
substrate/in-instructions/primitives/src/outgoing.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use scale::{Encode, Decode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use sp_core::{ConstU32, bounded::BoundedVec};
|
||||
|
||||
use serai_primitives::SeraiAddress;
|
||||
|
||||
use crate::{MAX_DATA_LEN, ExternalAddress};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub enum Destination {
|
||||
Native(SeraiAddress),
|
||||
External(ExternalAddress),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct OutInstruction {
|
||||
destination: Destination,
|
||||
data: Option<BoundedVec<u8, ConstU32<{ MAX_DATA_LEN }>>>,
|
||||
}
|
54
substrate/in-instructions/primitives/src/shorthand.rs
Normal file
54
substrate/in-instructions/primitives/src/shorthand.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use scale::{Encode, Decode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use sp_core::{ConstU32, bounded::BoundedVec};
|
||||
|
||||
use serai_primitives::{SeraiAddress, Coin, Amount};
|
||||
|
||||
use crate::{MAX_DATA_LEN, ExternalAddress, RefundableInInstruction, InInstruction, OutInstruction};
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub enum Shorthand {
|
||||
Raw(BoundedVec<u8, ConstU32<{ MAX_DATA_LEN }>>),
|
||||
Swap {
|
||||
origin: Option<ExternalAddress>,
|
||||
coin: Coin,
|
||||
minimum: Amount,
|
||||
out: OutInstruction,
|
||||
},
|
||||
AddLiquidity {
|
||||
origin: Option<ExternalAddress>,
|
||||
minimum: Amount,
|
||||
gas: Amount,
|
||||
address: SeraiAddress,
|
||||
},
|
||||
}
|
||||
|
||||
impl Shorthand {
|
||||
pub fn transfer(origin: Option<ExternalAddress>, address: SeraiAddress) -> Option<Self> {
|
||||
Some(Self::Raw(
|
||||
BoundedVec::try_from(
|
||||
(RefundableInInstruction { origin, instruction: InInstruction::Transfer(address) })
|
||||
.encode(),
|
||||
)
|
||||
.ok()?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Shorthand> for RefundableInInstruction {
|
||||
type Error = &'static str;
|
||||
fn try_from(shorthand: Shorthand) -> Result<RefundableInInstruction, &'static str> {
|
||||
Ok(match shorthand {
|
||||
Shorthand::Raw(raw) => {
|
||||
RefundableInInstruction::decode(&mut raw.as_ref()).map_err(|_| "invalid raw instruction")?
|
||||
}
|
||||
Shorthand::Swap { .. } => todo!(),
|
||||
Shorthand::AddLiquidity { .. } => todo!(),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -14,17 +14,11 @@ name = "serai-node"
|
|||
[dependencies]
|
||||
async-trait = "0.1"
|
||||
|
||||
log = "0.4"
|
||||
|
||||
futures = { version = "0.3" }
|
||||
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
|
||||
jsonrpsee = { version = "0.16", features = ["server"] }
|
||||
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate" }
|
||||
sp-application-crypto = { git = "https://github.com/serai-dex/substrate" }
|
||||
sp-keystore = { git = "https://github.com/serai-dex/substrate" }
|
||||
sp-keyring = { git = "https://github.com/serai-dex/substrate" }
|
||||
sp-inherents = { git = "https://github.com/serai-dex/substrate" }
|
||||
sp-runtime = { git = "https://github.com/serai-dex/substrate" }
|
||||
|
@ -33,7 +27,11 @@ sp-api = { git = "https://github.com/serai-dex/substrate" }
|
|||
sp-block-builder = { git = "https://github.com/serai-dex/substrate" }
|
||||
sp-consensus = { git = "https://github.com/serai-dex/substrate" }
|
||||
|
||||
sc-keystore = { git = "https://github.com/serai-dex/substrate" }
|
||||
frame-benchmarking = { git = "https://github.com/serai-dex/substrate" }
|
||||
frame-benchmarking-cli = { git = "https://github.com/serai-dex/substrate" }
|
||||
|
||||
serai-runtime = { path = "../runtime" }
|
||||
|
||||
sc-transaction-pool = { git = "https://github.com/serai-dex/substrate" }
|
||||
sc-transaction-pool-api = { git = "https://github.com/serai-dex/substrate" }
|
||||
sc-basic-authorship = { git = "https://github.com/serai-dex/substrate" }
|
||||
|
@ -47,24 +45,13 @@ sc-consensus = { git = "https://github.com/serai-dex/substrate" }
|
|||
sc-telemetry = { git = "https://github.com/serai-dex/substrate" }
|
||||
sc-cli = { git = "https://github.com/serai-dex/substrate" }
|
||||
|
||||
frame-system = { git = "https://github.com/serai-dex/substrate" }
|
||||
frame-benchmarking = { git = "https://github.com/serai-dex/substrate" }
|
||||
frame-benchmarking-cli = { git = "https://github.com/serai-dex/substrate" }
|
||||
pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
sc-rpc = { git = "https://github.com/serai-dex/substrate" }
|
||||
sc-rpc-api = { git = "https://github.com/serai-dex/substrate" }
|
||||
|
||||
substrate-frame-rpc-system = { git = "https://github.com/serai-dex/substrate" }
|
||||
pallet-transaction-payment-rpc = { git = "https://github.com/serai-dex/substrate" }
|
||||
|
||||
serai-primitives = { path = "../serai/primitives" }
|
||||
in-instructions-client = { path = "../in-instructions/client" }
|
||||
|
||||
validator-sets-pallet = { path = "../validator-sets/pallet" }
|
||||
|
||||
sp-tendermint = { path = "../tendermint/primitives" }
|
||||
pallet-tendermint = { path = "../tendermint/pallet", default-features = false }
|
||||
serai-runtime = { path = "../runtime" }
|
||||
sc-tendermint = { path = "../tendermint/client" }
|
||||
|
||||
[build-dependencies]
|
||||
|
|
|
@ -3,12 +3,9 @@ use sp_runtime::traits::TrailingZeroInput;
|
|||
|
||||
use sc_service::ChainType;
|
||||
|
||||
use serai_primitives::*;
|
||||
use pallet_tendermint::crypto::Public;
|
||||
|
||||
use serai_runtime::{
|
||||
WASM_BINARY, AccountId, opaque::SessionKeys, GenesisConfig, SystemConfig, BalancesConfig,
|
||||
AssetsConfig, ValidatorSetsConfig, SessionConfig,
|
||||
primitives::*, tendermint::crypto::Public, WASM_BINARY, opaque::SessionKeys, GenesisConfig,
|
||||
SystemConfig, BalancesConfig, AssetsConfig, ValidatorSetsConfig, SessionConfig,
|
||||
};
|
||||
|
||||
pub type ChainSpec = sc_service::GenericChainSpec<GenesisConfig>;
|
||||
|
@ -17,22 +14,22 @@ fn insecure_pair_from_name(name: &'static str) -> Pair {
|
|||
Pair::from_string(&format!("//{name}"), None).unwrap()
|
||||
}
|
||||
|
||||
fn account_id_from_name(name: &'static str) -> AccountId {
|
||||
fn address_from_name(name: &'static str) -> SeraiAddress {
|
||||
insecure_pair_from_name(name).public()
|
||||
}
|
||||
|
||||
fn testnet_genesis(
|
||||
wasm_binary: &[u8],
|
||||
validators: &[&'static str],
|
||||
endowed_accounts: Vec<AccountId>,
|
||||
endowed_accounts: Vec<SeraiAddress>,
|
||||
) -> GenesisConfig {
|
||||
let session_key = |name| {
|
||||
let key = account_id_from_name(name);
|
||||
let key = address_from_name(name);
|
||||
(key, key, SessionKeys { tendermint: Public::from(key) })
|
||||
};
|
||||
|
||||
// TODO: Replace with a call to the pallet to ask for its account
|
||||
let owner = AccountId::decode(&mut TrailingZeroInput::new(b"tokens")).unwrap();
|
||||
let owner = SeraiAddress::decode(&mut TrailingZeroInput::new(b"tokens")).unwrap();
|
||||
|
||||
GenesisConfig {
|
||||
system: SystemConfig { code: wasm_binary.to_vec() },
|
||||
|
@ -54,8 +51,8 @@ fn testnet_genesis(
|
|||
|
||||
validator_sets: ValidatorSetsConfig {
|
||||
bond: Amount(1_000_000) * COIN,
|
||||
coins: Coin(4),
|
||||
participants: validators.iter().map(|name| account_id_from_name(name)).collect(),
|
||||
coins: vec![BITCOIN, ETHER, DAI, MONERO],
|
||||
participants: validators.iter().map(|name| address_from_name(name)).collect(),
|
||||
},
|
||||
session: SessionConfig { keys: validators.iter().map(|name| session_key(*name)).collect() },
|
||||
}
|
||||
|
@ -75,18 +72,18 @@ pub fn development_config() -> Result<ChainSpec, &'static str> {
|
|||
wasm_binary,
|
||||
&["Alice"],
|
||||
vec![
|
||||
account_id_from_name("Alice"),
|
||||
account_id_from_name("Bob"),
|
||||
account_id_from_name("Charlie"),
|
||||
account_id_from_name("Dave"),
|
||||
account_id_from_name("Eve"),
|
||||
account_id_from_name("Ferdie"),
|
||||
account_id_from_name("Alice//stash"),
|
||||
account_id_from_name("Bob//stash"),
|
||||
account_id_from_name("Charlie//stash"),
|
||||
account_id_from_name("Dave//stash"),
|
||||
account_id_from_name("Eve//stash"),
|
||||
account_id_from_name("Ferdie//stash"),
|
||||
address_from_name("Alice"),
|
||||
address_from_name("Bob"),
|
||||
address_from_name("Charlie"),
|
||||
address_from_name("Dave"),
|
||||
address_from_name("Eve"),
|
||||
address_from_name("Ferdie"),
|
||||
address_from_name("Alice//stash"),
|
||||
address_from_name("Bob//stash"),
|
||||
address_from_name("Charlie//stash"),
|
||||
address_from_name("Dave//stash"),
|
||||
address_from_name("Eve//stash"),
|
||||
address_from_name("Ferdie//stash"),
|
||||
],
|
||||
)
|
||||
},
|
||||
|
@ -119,18 +116,18 @@ pub fn testnet_config() -> Result<ChainSpec, &'static str> {
|
|||
wasm_binary,
|
||||
&["Alice", "Bob", "Charlie"],
|
||||
vec![
|
||||
account_id_from_name("Alice"),
|
||||
account_id_from_name("Bob"),
|
||||
account_id_from_name("Charlie"),
|
||||
account_id_from_name("Dave"),
|
||||
account_id_from_name("Eve"),
|
||||
account_id_from_name("Ferdie"),
|
||||
account_id_from_name("Alice//stash"),
|
||||
account_id_from_name("Bob//stash"),
|
||||
account_id_from_name("Charlie//stash"),
|
||||
account_id_from_name("Dave//stash"),
|
||||
account_id_from_name("Eve//stash"),
|
||||
account_id_from_name("Ferdie//stash"),
|
||||
address_from_name("Alice"),
|
||||
address_from_name("Bob"),
|
||||
address_from_name("Charlie"),
|
||||
address_from_name("Dave"),
|
||||
address_from_name("Eve"),
|
||||
address_from_name("Ferdie"),
|
||||
address_from_name("Alice//stash"),
|
||||
address_from_name("Bob//stash"),
|
||||
address_from_name("Charlie//stash"),
|
||||
address_from_name("Dave//stash"),
|
||||
address_from_name("Eve//stash"),
|
||||
address_from_name("Ferdie//stash"),
|
||||
],
|
||||
)
|
||||
},
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use sc_service::{PruningMode, PartialComponents};
|
||||
use frame_benchmarking_cli::{ExtrinsicFactory, BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE};
|
||||
use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli};
|
||||
|
||||
use serai_runtime::Block;
|
||||
|
||||
use sc_service::{PruningMode, PartialComponents};
|
||||
|
||||
use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli};
|
||||
use frame_benchmarking_cli::{ExtrinsicFactory, BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE};
|
||||
|
||||
use crate::{
|
||||
chain_spec,
|
||||
cli::{Cli, Subcommand},
|
||||
|
|
|
@ -9,8 +9,11 @@ use sp_runtime::OpaqueExtrinsic;
|
|||
use sc_cli::Result;
|
||||
use sc_client_api::BlockBackend;
|
||||
|
||||
use serai_runtime as runtime;
|
||||
use runtime::SystemCall;
|
||||
use serai_runtime::{
|
||||
VERSION, BlockHashCount,
|
||||
system::{self, Call as SystemCall},
|
||||
transaction_payment, RuntimeCall, UncheckedExtrinsic, SignedPayload, Runtime,
|
||||
};
|
||||
|
||||
use crate::service::FullClient;
|
||||
|
||||
|
@ -45,35 +48,33 @@ impl frame_benchmarking_cli::ExtrinsicBuilder for RemarkBuilder {
|
|||
pub fn create_benchmark_extrinsic(
|
||||
client: &FullClient,
|
||||
sender: sp_core::sr25519::Pair,
|
||||
call: runtime::RuntimeCall,
|
||||
call: RuntimeCall,
|
||||
nonce: u32,
|
||||
) -> runtime::UncheckedExtrinsic {
|
||||
) -> UncheckedExtrinsic {
|
||||
let extra = (
|
||||
frame_system::CheckNonZeroSender::<runtime::Runtime>::new(),
|
||||
frame_system::CheckSpecVersion::<runtime::Runtime>::new(),
|
||||
frame_system::CheckTxVersion::<runtime::Runtime>::new(),
|
||||
frame_system::CheckGenesis::<runtime::Runtime>::new(),
|
||||
frame_system::CheckEra::<runtime::Runtime>::from(sp_runtime::generic::Era::mortal(
|
||||
u64::from(
|
||||
runtime::BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2),
|
||||
),
|
||||
system::CheckNonZeroSender::<Runtime>::new(),
|
||||
system::CheckSpecVersion::<Runtime>::new(),
|
||||
system::CheckTxVersion::<Runtime>::new(),
|
||||
system::CheckGenesis::<Runtime>::new(),
|
||||
system::CheckEra::<Runtime>::from(sp_runtime::generic::Era::mortal(
|
||||
u64::from(BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2)),
|
||||
client.chain_info().best_number.into(),
|
||||
)),
|
||||
frame_system::CheckNonce::<runtime::Runtime>::from(nonce),
|
||||
frame_system::CheckWeight::<runtime::Runtime>::new(),
|
||||
pallet_transaction_payment::ChargeTransactionPayment::<runtime::Runtime>::from(0),
|
||||
system::CheckNonce::<Runtime>::from(nonce),
|
||||
system::CheckWeight::<Runtime>::new(),
|
||||
transaction_payment::ChargeTransactionPayment::<Runtime>::from(0),
|
||||
);
|
||||
|
||||
runtime::UncheckedExtrinsic::new_signed(
|
||||
UncheckedExtrinsic::new_signed(
|
||||
call.clone(),
|
||||
sender.public(),
|
||||
runtime::SignedPayload::from_raw(
|
||||
SignedPayload::from_raw(
|
||||
call,
|
||||
extra.clone(),
|
||||
(
|
||||
(),
|
||||
runtime::VERSION.spec_version,
|
||||
runtime::VERSION.transaction_version,
|
||||
VERSION.spec_version,
|
||||
VERSION.transaction_version,
|
||||
client.block_hash(0).ok().flatten().unwrap(),
|
||||
client.chain_info().best_hash,
|
||||
(),
|
||||
|
|
|
@ -3,13 +3,13 @@ use std::sync::Arc;
|
|||
use jsonrpsee::RpcModule;
|
||||
|
||||
use sp_blockchain::{Error as BlockchainError, HeaderBackend, HeaderMetadata};
|
||||
use sc_transaction_pool_api::TransactionPool;
|
||||
use sp_block_builder::BlockBuilder;
|
||||
use sp_api::ProvideRuntimeApi;
|
||||
|
||||
pub use sc_rpc_api::DenyUnsafe;
|
||||
use serai_runtime::{primitives::SeraiAddress, opaque::Block, Balance, Index};
|
||||
|
||||
use serai_runtime::{opaque::Block, AccountId, Balance, Index};
|
||||
pub use sc_rpc_api::DenyUnsafe;
|
||||
use sc_transaction_pool_api::TransactionPool;
|
||||
|
||||
pub struct FullDeps<C, P> {
|
||||
pub client: Arc<C>,
|
||||
|
@ -29,7 +29,7 @@ pub fn create_full<
|
|||
deps: FullDeps<C, P>,
|
||||
) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>>
|
||||
where
|
||||
C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Index>
|
||||
C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, SeraiAddress, Index>
|
||||
+ pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, Balance>
|
||||
+ BlockBuilder<Block>,
|
||||
{
|
||||
|
|
|
@ -11,6 +11,8 @@ use sp_inherents::CreateInherentDataProviders;
|
|||
use sp_consensus::DisableProofRecording;
|
||||
use sp_api::ProvideRuntimeApi;
|
||||
|
||||
use in_instructions_client::InherentDataProvider as InstructionsProvider;
|
||||
|
||||
use sc_executor::{NativeVersion, NativeExecutionDispatch, NativeElseWasmExecutor};
|
||||
use sc_transaction_pool::FullPool;
|
||||
use sc_network::NetworkService;
|
||||
|
@ -24,7 +26,7 @@ pub(crate) use sc_tendermint::{
|
|||
TendermintClientMinimal, TendermintValidator, TendermintImport, TendermintAuthority,
|
||||
TendermintSelectChain, import_queue,
|
||||
};
|
||||
use serai_runtime::{self, BLOCK_SIZE, TARGET_BLOCK_TIME, opaque::Block, RuntimeApi};
|
||||
use serai_runtime::{self as runtime, BLOCK_SIZE, TARGET_BLOCK_TIME, opaque::Block, RuntimeApi};
|
||||
|
||||
type FullBackend = sc_service::TFullBackend<Block>;
|
||||
pub type FullClient = TFullClient<Block, RuntimeApi, NativeElseWasmExecutor<ExecutorDispatch>>;
|
||||
|
@ -46,7 +48,7 @@ impl NativeExecutionDispatch for ExecutorDispatch {
|
|||
type ExtendHostFunctions = ();
|
||||
|
||||
fn dispatch(method: &str, data: &[u8]) -> Option<Vec<u8>> {
|
||||
serai_runtime::api::dispatch(method, data)
|
||||
runtime::api::dispatch(method, data)
|
||||
}
|
||||
|
||||
fn native_version() -> NativeVersion {
|
||||
|
@ -57,13 +59,13 @@ impl NativeExecutionDispatch for ExecutorDispatch {
|
|||
pub struct Cidp;
|
||||
#[async_trait::async_trait]
|
||||
impl CreateInherentDataProviders<Block, ()> for Cidp {
|
||||
type InherentDataProviders = ();
|
||||
type InherentDataProviders = (InstructionsProvider,);
|
||||
async fn create_inherent_data_providers(
|
||||
&self,
|
||||
_: <Block as BlockTrait>::Hash,
|
||||
_: (),
|
||||
) -> Result<Self::InherentDataProviders, Box<dyn Send + Sync + Error>> {
|
||||
Ok(())
|
||||
Ok((InstructionsProvider::new(),))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,13 +12,12 @@ all-features = true
|
|||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
hex-literal = { version = "0.3.4", optional = true }
|
||||
hex-literal = { version = "0.3", optional = true }
|
||||
|
||||
codec = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
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 }
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-version = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-inherents = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
@ -42,6 +41,8 @@ pallet-balances = { git = "https://github.com/serai-dex/substrate", default-feat
|
|||
pallet-assets = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
in-instructions-pallet = { path = "../in-instructions/pallet", default-features = false }
|
||||
|
||||
validator-sets-pallet = { path = "../validator-sets/pallet", default-features = false }
|
||||
pallet-session = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
pallet-tendermint = { path = "../tendermint/pallet", default-features = false }
|
||||
|
@ -58,7 +59,6 @@ std = [
|
|||
"scale-info/std",
|
||||
|
||||
"sp-core/std",
|
||||
"sp-application-crypto/std",
|
||||
"sp-std/std",
|
||||
"sp-version/std",
|
||||
"sp-inherents/std",
|
||||
|
@ -81,6 +81,8 @@ std = [
|
|||
"pallet-assets/std",
|
||||
"pallet-transaction-payment/std",
|
||||
|
||||
"in-instructions-pallet/std",
|
||||
|
||||
"validator-sets-pallet/std",
|
||||
"pallet-session/std",
|
||||
"pallet-tendermint/std",
|
||||
|
|
|
@ -1,23 +1,46 @@
|
|||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
|
||||
|
||||
// Re-export all components
|
||||
pub use serai_primitives as primitives;
|
||||
|
||||
pub use frame_system as system;
|
||||
pub use frame_support as support;
|
||||
|
||||
pub use pallet_balances as balances;
|
||||
pub use pallet_transaction_payment as transaction_payment;
|
||||
|
||||
pub use pallet_assets as assets;
|
||||
pub use in_instructions_pallet as in_instructions;
|
||||
|
||||
pub use validator_sets_pallet as validator_sets;
|
||||
|
||||
pub use pallet_session as session;
|
||||
pub use pallet_tendermint as tendermint;
|
||||
|
||||
// Actually used by the runtime
|
||||
use sp_core::OpaqueMetadata;
|
||||
pub use sp_core::sr25519::{Public, Signature};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
use sp_version::RuntimeVersion;
|
||||
#[cfg(feature = "std")]
|
||||
use sp_version::NativeVersion;
|
||||
|
||||
use sp_runtime::{
|
||||
create_runtime_str, generic, impl_opaque_keys, KeyTypeId,
|
||||
traits::{Convert, OpaqueKeys, IdentityLookup, BlakeTwo256, Block as BlockT},
|
||||
transaction_validity::{TransactionSource, TransactionValidity},
|
||||
ApplyExtrinsicResult, Perbill,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
#[cfg(feature = "std")]
|
||||
use sp_version::NativeVersion;
|
||||
use sp_version::RuntimeVersion;
|
||||
|
||||
use frame_support::{
|
||||
use primitives::{PublicKey, Signature, SeraiAddress, Coin};
|
||||
|
||||
use support::{
|
||||
traits::{ConstU8, ConstU32, ConstU64},
|
||||
weights::{
|
||||
constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND},
|
||||
|
@ -25,22 +48,14 @@ use frame_support::{
|
|||
},
|
||||
parameter_types, construct_runtime,
|
||||
};
|
||||
pub use frame_system::Call as SystemCall;
|
||||
|
||||
use serai_primitives::Coin;
|
||||
use transaction_payment::CurrencyAdapter;
|
||||
|
||||
pub use pallet_balances::Call as BalancesCall;
|
||||
pub use pallet_assets::Call as AssetsCall;
|
||||
use pallet_transaction_payment::CurrencyAdapter;
|
||||
|
||||
use pallet_session::PeriodicSessions;
|
||||
use session::PeriodicSessions;
|
||||
|
||||
/// An index to a block.
|
||||
pub type BlockNumber = u32;
|
||||
|
||||
/// Account ID type, equivalent to a public key
|
||||
pub type AccountId = Public;
|
||||
|
||||
/// Balance of an account.
|
||||
// Distinct from serai-primitives Amount due to Substrate's requirements on this type.
|
||||
// If Amount could be dropped in here, it would be.
|
||||
|
@ -58,7 +73,7 @@ pub type Hash = sp_core::H256;
|
|||
pub mod opaque {
|
||||
use super::*;
|
||||
|
||||
pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic;
|
||||
use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic;
|
||||
|
||||
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
|
||||
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||
|
@ -110,22 +125,22 @@ parameter_types! {
|
|||
pub const SS58Prefix: u8 = 42; // TODO: Remove for Bech32m
|
||||
|
||||
// 1 MB block size limit
|
||||
pub BlockLength: frame_system::limits::BlockLength =
|
||||
frame_system::limits::BlockLength::max_with_normal_ratio(BLOCK_SIZE, NORMAL_DISPATCH_RATIO);
|
||||
pub BlockWeights: frame_system::limits::BlockWeights =
|
||||
frame_system::limits::BlockWeights::with_sensible_defaults(
|
||||
pub BlockLength: system::limits::BlockLength =
|
||||
system::limits::BlockLength::max_with_normal_ratio(BLOCK_SIZE, NORMAL_DISPATCH_RATIO);
|
||||
pub BlockWeights: system::limits::BlockWeights =
|
||||
system::limits::BlockWeights::with_sensible_defaults(
|
||||
Weight::from_ref_time(2u64 * WEIGHT_REF_TIME_PER_SECOND).set_proof_size(u64::MAX),
|
||||
NORMAL_DISPATCH_RATIO,
|
||||
);
|
||||
}
|
||||
|
||||
impl frame_system::Config for Runtime {
|
||||
type BaseCallFilter = frame_support::traits::Everything;
|
||||
impl system::Config for Runtime {
|
||||
type BaseCallFilter = support::traits::Everything;
|
||||
type BlockWeights = BlockWeights;
|
||||
type BlockLength = BlockLength;
|
||||
type AccountId = AccountId;
|
||||
type AccountId = SeraiAddress;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Lookup = IdentityLookup<AccountId>;
|
||||
type Lookup = IdentityLookup<SeraiAddress>;
|
||||
type Index = Index;
|
||||
type BlockNumber = BlockNumber;
|
||||
type Hash = Hash;
|
||||
|
@ -142,14 +157,14 @@ impl frame_system::Config for Runtime {
|
|||
type OnKilledAccount = ();
|
||||
type OnSetCode = ();
|
||||
|
||||
type AccountData = pallet_balances::AccountData<Balance>;
|
||||
type AccountData = balances::AccountData<Balance>;
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = SS58Prefix; // TODO: Remove for Bech32m
|
||||
|
||||
type MaxConsumers = frame_support::traits::ConstU32<16>;
|
||||
type MaxConsumers = support::traits::ConstU32<16>;
|
||||
}
|
||||
|
||||
impl pallet_balances::Config for Runtime {
|
||||
impl balances::Config for Runtime {
|
||||
type MaxLocks = ConstU32<50>;
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
|
@ -158,10 +173,10 @@ impl pallet_balances::Config for Runtime {
|
|||
type DustRemoval = ();
|
||||
type ExistentialDeposit = ConstU64<500>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = pallet_balances::weights::SubstrateWeight<Runtime>;
|
||||
type WeightInfo = balances::weights::SubstrateWeight<Runtime>;
|
||||
}
|
||||
|
||||
impl pallet_assets::Config for Runtime {
|
||||
impl assets::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Balance = Balance;
|
||||
type Currency = Balances;
|
||||
|
@ -171,9 +186,8 @@ impl pallet_assets::Config for Runtime {
|
|||
type StringLimit = ConstU32<32>;
|
||||
|
||||
// Don't allow anyone to create assets
|
||||
type CreateOrigin =
|
||||
frame_support::traits::AsEnsureOriginWithArg<frame_system::EnsureNever<AccountId>>;
|
||||
type ForceOrigin = frame_system::EnsureRoot<AccountId>;
|
||||
type CreateOrigin = support::traits::AsEnsureOriginWithArg<system::EnsureNever<SeraiAddress>>;
|
||||
type ForceOrigin = system::EnsureRoot<SeraiAddress>;
|
||||
|
||||
// Don't charge fees nor kill accounts
|
||||
type RemoveItemsLimit = ConstU32<0>;
|
||||
|
@ -188,12 +202,12 @@ impl pallet_assets::Config for Runtime {
|
|||
type Freezer = ();
|
||||
type Extra = ();
|
||||
|
||||
type WeightInfo = pallet_assets::weights::SubstrateWeight<Runtime>;
|
||||
type WeightInfo = assets::weights::SubstrateWeight<Runtime>;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
||||
impl pallet_transaction_payment::Config for Runtime {
|
||||
impl transaction_payment::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type OnChargeTransaction = CurrencyAdapter<Balances, ()>;
|
||||
type OperationalFeeMultiplier = ConstU8<5>;
|
||||
|
@ -202,54 +216,57 @@ impl pallet_transaction_payment::Config for Runtime {
|
|||
type FeeMultiplierUpdate = ();
|
||||
}
|
||||
|
||||
impl in_instructions::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
const SESSION_LENGTH: BlockNumber = 5 * DAYS;
|
||||
type Sessions = PeriodicSessions<ConstU32<{ SESSION_LENGTH }>, ConstU32<{ SESSION_LENGTH }>>;
|
||||
|
||||
pub struct IdentityValidatorIdOf;
|
||||
impl Convert<Public, Option<Public>> for IdentityValidatorIdOf {
|
||||
fn convert(key: Public) -> Option<Public> {
|
||||
impl Convert<PublicKey, Option<PublicKey>> for IdentityValidatorIdOf {
|
||||
fn convert(key: PublicKey) -> Option<PublicKey> {
|
||||
Some(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl validator_sets_pallet::Config for Runtime {
|
||||
impl validator_sets::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
impl pallet_session::Config for Runtime {
|
||||
impl session::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ValidatorId = AccountId;
|
||||
type ValidatorId = SeraiAddress;
|
||||
type ValidatorIdOf = IdentityValidatorIdOf;
|
||||
type ShouldEndSession = Sessions;
|
||||
type NextSessionRotation = Sessions;
|
||||
type SessionManager = ();
|
||||
type SessionHandler = <SessionKeys as OpaqueKeys>::KeyTypeIdProviders;
|
||||
type Keys = SessionKeys;
|
||||
type WeightInfo = pallet_session::weights::SubstrateWeight<Runtime>;
|
||||
type WeightInfo = session::weights::SubstrateWeight<Runtime>;
|
||||
}
|
||||
|
||||
impl pallet_tendermint::Config for Runtime {}
|
||||
impl tendermint::Config for Runtime {}
|
||||
|
||||
pub type Address = AccountId;
|
||||
pub type Header = generic::Header<BlockNumber, BlakeTwo256>;
|
||||
pub type Block = generic::Block<Header, UncheckedExtrinsic>;
|
||||
pub type SignedExtra = (
|
||||
frame_system::CheckNonZeroSender<Runtime>,
|
||||
frame_system::CheckSpecVersion<Runtime>,
|
||||
frame_system::CheckTxVersion<Runtime>,
|
||||
frame_system::CheckGenesis<Runtime>,
|
||||
frame_system::CheckEra<Runtime>,
|
||||
frame_system::CheckNonce<Runtime>,
|
||||
frame_system::CheckWeight<Runtime>,
|
||||
pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
|
||||
system::CheckNonZeroSender<Runtime>,
|
||||
system::CheckSpecVersion<Runtime>,
|
||||
system::CheckTxVersion<Runtime>,
|
||||
system::CheckGenesis<Runtime>,
|
||||
system::CheckEra<Runtime>,
|
||||
system::CheckNonce<Runtime>,
|
||||
system::CheckWeight<Runtime>,
|
||||
transaction_payment::ChargeTransactionPayment<Runtime>,
|
||||
);
|
||||
pub type UncheckedExtrinsic =
|
||||
generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, SignedExtra>;
|
||||
generic::UncheckedExtrinsic<SeraiAddress, RuntimeCall, Signature, SignedExtra>;
|
||||
pub type SignedPayload = generic::SignedPayload<RuntimeCall, SignedExtra>;
|
||||
pub type Executive = frame_executive::Executive<
|
||||
Runtime,
|
||||
Block,
|
||||
frame_system::ChainContext<Runtime>,
|
||||
system::ChainContext<Runtime>,
|
||||
Runtime,
|
||||
AllPalletsWithSystem,
|
||||
>;
|
||||
|
@ -260,14 +277,18 @@ construct_runtime!(
|
|||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic
|
||||
{
|
||||
System: frame_system,
|
||||
Balances: pallet_balances,
|
||||
Assets: pallet_assets,
|
||||
TransactionPayment: pallet_transaction_payment,
|
||||
System: system,
|
||||
|
||||
ValidatorSets: validator_sets_pallet,
|
||||
Session: pallet_session,
|
||||
Tendermint: pallet_tendermint,
|
||||
Balances: balances,
|
||||
TransactionPayment: transaction_payment,
|
||||
|
||||
Assets: assets,
|
||||
InInstructions: in_instructions,
|
||||
|
||||
ValidatorSets: validator_sets,
|
||||
|
||||
Session: session,
|
||||
Tendermint: tendermint,
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -279,8 +300,8 @@ extern crate frame_benchmarking;
|
|||
mod benches {
|
||||
define_benchmarks!(
|
||||
[frame_benchmarking, BaselineBench::<Runtime>]
|
||||
[frame_system, SystemBench::<Runtime>]
|
||||
[pallet_balances, Balances]
|
||||
[system, SystemBench::<Runtime>]
|
||||
[balances, Balances]
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -359,13 +380,13 @@ sp_api::impl_runtime_apis! {
|
|||
Tendermint::session()
|
||||
}
|
||||
|
||||
fn validators() -> Vec<Public> {
|
||||
fn validators() -> Vec<PublicKey> {
|
||||
Session::validators()
|
||||
}
|
||||
}
|
||||
|
||||
impl frame_system_rpc_runtime_api::AccountNonceApi<Block, AccountId, Index> for Runtime {
|
||||
fn account_nonce(account: AccountId) -> Index {
|
||||
impl frame_system_rpc_runtime_api::AccountNonceApi<Block, SeraiAddress, Index> for Runtime {
|
||||
fn account_nonce(account: SeraiAddress) -> Index {
|
||||
System::account_nonce(account)
|
||||
}
|
||||
}
|
||||
|
@ -380,10 +401,11 @@ sp_api::impl_runtime_apis! {
|
|||
) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo<Balance> {
|
||||
TransactionPayment::query_info(uxt, len)
|
||||
}
|
||||
|
||||
fn query_fee_details(
|
||||
uxt: <Block as BlockT>::Extrinsic,
|
||||
len: u32,
|
||||
) -> pallet_transaction_payment::FeeDetails<Balance> {
|
||||
) -> transaction_payment::FeeDetails<Balance> {
|
||||
TransactionPayment::query_fee_details(uxt, len)
|
||||
}
|
||||
}
|
||||
|
|
33
substrate/serai/client/Cargo.toml
Normal file
33
substrate/serai/client/Cargo.toml
Normal file
|
@ -0,0 +1,33 @@
|
|||
[package]
|
||||
name = "serai-client"
|
||||
version = "0.1.0"
|
||||
description = "Client library for the Serai network"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/client"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = ["serai"]
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1"
|
||||
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3" }
|
||||
scale-value = "0.6"
|
||||
subxt = "0.25"
|
||||
|
||||
serai-primitives = { path = "../primitives", version = "0.1" }
|
||||
in-instructions-primitives = { path = "../../in-instructions/primitives", version = "0.1" }
|
||||
serai-runtime = { path = "../../runtime", version = "0.1" }
|
||||
|
||||
[dev-dependencies]
|
||||
lazy_static = "1"
|
||||
|
||||
tokio = "1"
|
||||
|
||||
jsonrpsee-server = "0.16"
|
15
substrate/serai/client/LICENSE
Normal file
15
substrate/serai/client/LICENSE
Normal file
|
@ -0,0 +1,15 @@
|
|||
AGPL-3.0-only license
|
||||
|
||||
Copyright (c) 2022-2023 Luke Parker
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License Version 3 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
49
substrate/serai/client/src/in_instructions.rs
Normal file
49
substrate/serai/client/src/in_instructions.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use scale::Decode;
|
||||
|
||||
use serai_runtime::{
|
||||
support::traits::PalletInfo as PalletInfoTrait, PalletInfo, in_instructions, InInstructions,
|
||||
Runtime,
|
||||
};
|
||||
|
||||
pub use in_instructions_primitives as primitives;
|
||||
|
||||
use crate::{
|
||||
primitives::{Coin, BlockNumber},
|
||||
Serai, SeraiError,
|
||||
};
|
||||
|
||||
const PALLET: &str = "InInstructions";
|
||||
|
||||
pub type InInstructionsEvent = in_instructions::Event<Runtime>;
|
||||
|
||||
impl Serai {
|
||||
pub async fn get_batch_events(
|
||||
&self,
|
||||
block: [u8; 32],
|
||||
) -> Result<Vec<InInstructionsEvent>, SeraiError> {
|
||||
let mut res = vec![];
|
||||
for event in
|
||||
self.0.events().at(Some(block.into())).await.map_err(|_| SeraiError::RpcError)?.iter()
|
||||
{
|
||||
let event = event.map_err(|_| SeraiError::InvalidRuntime)?;
|
||||
if PalletInfo::index::<InInstructions>().unwrap() == usize::from(event.pallet_index()) {
|
||||
let mut with_variant: &[u8] =
|
||||
&[[event.variant_index()].as_ref(), event.field_bytes()].concat();
|
||||
let event =
|
||||
InInstructionsEvent::decode(&mut with_variant).map_err(|_| SeraiError::InvalidRuntime)?;
|
||||
if matches!(event, InInstructionsEvent::Batch { .. }) {
|
||||
res.push(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn get_coin_block_number(
|
||||
&self,
|
||||
coin: Coin,
|
||||
block: [u8; 32],
|
||||
) -> Result<BlockNumber, SeraiError> {
|
||||
Ok(self.storage(PALLET, "BlockNumbers", Some(coin), block).await?.unwrap_or(BlockNumber(0)))
|
||||
}
|
||||
}
|
77
substrate/serai/client/src/lib.rs
Normal file
77
substrate/serai/client/src/lib.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
use thiserror::Error;
|
||||
|
||||
use serde::Serialize;
|
||||
use scale::Decode;
|
||||
|
||||
use subxt::{tx::BaseExtrinsicParams, Config as SubxtConfig, OnlineClient};
|
||||
|
||||
pub use serai_primitives as primitives;
|
||||
use primitives::{Signature, SeraiAddress};
|
||||
|
||||
use serai_runtime::{system::Config, Runtime};
|
||||
|
||||
pub mod in_instructions;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub(crate) struct SeraiConfig;
|
||||
impl SubxtConfig for SeraiConfig {
|
||||
type BlockNumber = <Runtime as Config>::BlockNumber;
|
||||
|
||||
type Hash = <Runtime as Config>::Hash;
|
||||
type Hashing = <Runtime as Config>::Hashing;
|
||||
|
||||
type Index = <Runtime as Config>::Index;
|
||||
type AccountId = <Runtime as Config>::AccountId;
|
||||
// TODO: Bech32m
|
||||
type Address = SeraiAddress;
|
||||
|
||||
type Header = <Runtime as Config>::Header;
|
||||
type Signature = Signature;
|
||||
|
||||
type ExtrinsicParams = BaseExtrinsicParams<SeraiConfig, ()>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Error, Debug)]
|
||||
pub enum SeraiError {
|
||||
#[error("failed to connect to serai")]
|
||||
RpcError,
|
||||
#[error("serai-client library was intended for a different runtime version")]
|
||||
InvalidRuntime,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Serai(OnlineClient<SeraiConfig>);
|
||||
|
||||
impl Serai {
|
||||
pub async fn new(url: &str) -> Result<Self, SeraiError> {
|
||||
Ok(Serai(OnlineClient::<SeraiConfig>::from_url(url).await.map_err(|_| SeraiError::RpcError)?))
|
||||
}
|
||||
|
||||
async fn storage<K: Serialize, R: Decode>(
|
||||
&self,
|
||||
pallet: &'static str,
|
||||
name: &'static str,
|
||||
key: Option<K>,
|
||||
block: [u8; 32],
|
||||
) -> Result<Option<R>, SeraiError> {
|
||||
let mut keys = vec![];
|
||||
if let Some(key) = key {
|
||||
keys.push(scale_value::serde::to_value(key).unwrap());
|
||||
}
|
||||
|
||||
let storage = self.0.storage();
|
||||
let address = subxt::dynamic::storage(pallet, name, keys);
|
||||
debug_assert!(storage.validate(&address).is_ok());
|
||||
|
||||
storage
|
||||
.fetch(&address, Some(block.into()))
|
||||
.await
|
||||
.map_err(|_| SeraiError::RpcError)?
|
||||
.map(|res| R::decode(&mut res.encoded()).map_err(|_| SeraiError::InvalidRuntime))
|
||||
.transpose()
|
||||
}
|
||||
|
||||
pub async fn get_latest_block_hash(&self) -> Result<[u8; 32], SeraiError> {
|
||||
Ok(self.0.rpc().finalized_head().await.map_err(|_| SeraiError::RpcError)?.into())
|
||||
}
|
||||
}
|
50
substrate/serai/client/tests/runner.rs
Normal file
50
substrate/serai/client/tests/runner.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use lazy_static::lazy_static;
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub const URL: &str = "ws://127.0.0.1:9944";
|
||||
|
||||
lazy_static! {
|
||||
pub static ref SEQUENTIAL: Mutex<()> = Mutex::new(());
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! serai_test {
|
||||
($(async fn $name: ident() $body: block)*) => {
|
||||
$(
|
||||
#[tokio::test]
|
||||
async fn $name() {
|
||||
let guard = runner::SEQUENTIAL.lock().await;
|
||||
|
||||
// Spawn a fresh Serai node
|
||||
let mut command = {
|
||||
use core::time::Duration;
|
||||
use std::{path::Path, process::Command};
|
||||
|
||||
let node = {
|
||||
let this_crate = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let top_level = this_crate.join("../../../");
|
||||
top_level.join("target/debug/serai-node")
|
||||
};
|
||||
|
||||
let command = Command::new(node).arg("--dev").spawn().unwrap();
|
||||
while Serai::new(URL).await.is_err() {
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
command
|
||||
};
|
||||
|
||||
let local = tokio::task::LocalSet::new();
|
||||
local.run_until(async move {
|
||||
if let Err(err) = tokio::task::spawn_local(async move { $body }).await {
|
||||
drop(guard);
|
||||
let _ = command.kill();
|
||||
Err(err).unwrap()
|
||||
} else {
|
||||
command.kill().unwrap();
|
||||
}
|
||||
}).await;
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
60
substrate/serai/client/tests/updates.rs
Normal file
60
substrate/serai/client/tests/updates.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use core::time::Duration;
|
||||
|
||||
use tokio::time::sleep;
|
||||
|
||||
use serai_runtime::in_instructions::{Batch, Update};
|
||||
|
||||
use jsonrpsee_server::RpcModule;
|
||||
|
||||
use serai_client::{
|
||||
primitives::{BlockNumber, BlockHash, SeraiAddress, BITCOIN},
|
||||
in_instructions::{primitives::InInstruction, InInstructionsEvent},
|
||||
Serai,
|
||||
};
|
||||
|
||||
mod runner;
|
||||
use runner::URL;
|
||||
|
||||
serai_test!(
|
||||
async fn publish_update() {
|
||||
let mut rpc = RpcModule::new(());
|
||||
rpc
|
||||
.register_async_method("processor_coinUpdates", |_, _| async move {
|
||||
let batch = Batch {
|
||||
id: BlockHash([0xaa; 32]),
|
||||
instructions: vec![InInstruction::Transfer(SeraiAddress::from_raw([0xff; 32]))],
|
||||
};
|
||||
|
||||
Ok(vec![Some(Update { block_number: BlockNumber(123), batches: vec![batch] })])
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let _handle = jsonrpsee_server::ServerBuilder::default()
|
||||
.build("127.0.0.1:5134")
|
||||
.await
|
||||
.unwrap()
|
||||
.start(rpc)
|
||||
.unwrap();
|
||||
|
||||
let serai = Serai::new(URL).await.unwrap();
|
||||
loop {
|
||||
let latest = serai.get_latest_block_hash().await.unwrap();
|
||||
let batches = serai.get_batch_events(latest).await.unwrap();
|
||||
if let Some(batch) = batches.get(0) {
|
||||
match batch {
|
||||
InInstructionsEvent::Batch { coin, id } => {
|
||||
assert_eq!(coin, &BITCOIN);
|
||||
assert_eq!(id, &BlockHash([0xaa; 32]));
|
||||
assert_eq!(
|
||||
serai.get_coin_block_number(BITCOIN, latest).await.unwrap(),
|
||||
BlockNumber(123)
|
||||
);
|
||||
return;
|
||||
}
|
||||
_ => panic!("get_batches returned non-batch"),
|
||||
}
|
||||
}
|
||||
sleep(Duration::from_millis(50)).await;
|
||||
}
|
||||
}
|
||||
);
|
|
@ -15,11 +15,10 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
|
||||
[features]
|
||||
std = ["scale/std", "scale-info/std", "serde", "sp-core/std", "sp-std/std"]
|
||||
std = ["scale/std", "scale-info/std", "serde", "sp-core/std"]
|
||||
default = ["std"]
|
||||
|
|
|
@ -9,7 +9,7 @@ use serde::{Serialize, Deserialize};
|
|||
|
||||
/// The type used for amounts.
|
||||
#[derive(
|
||||
Clone, Copy, PartialEq, Eq, PartialOrd, Debug, Encode, Decode, TypeInfo, MaxEncodedLen,
|
||||
Clone, Copy, PartialEq, Eq, PartialOrd, Debug, Encode, Decode, MaxEncodedLen, TypeInfo,
|
||||
)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct Amount(pub u64);
|
||||
|
|
|
@ -4,7 +4,7 @@ use scale_info::TypeInfo;
|
|||
use serde::{Serialize, Deserialize};
|
||||
|
||||
/// The type used to identify coins.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct Coin(pub u32);
|
||||
impl From<u32> for Coin {
|
||||
|
@ -13,7 +13,8 @@ impl From<u32> for Coin {
|
|||
}
|
||||
}
|
||||
|
||||
pub const BITCOIN: Coin = Coin(0);
|
||||
pub const ETHER: Coin = Coin(1);
|
||||
pub const DAI: Coin = Coin(2);
|
||||
pub const MONERO: Coin = Coin(3);
|
||||
pub const SERAI: Coin = Coin(0);
|
||||
pub const BITCOIN: Coin = Coin(1);
|
||||
pub const ETHER: Coin = Coin(2);
|
||||
pub const DAI: Coin = Coin(3);
|
||||
pub const MONERO: Coin = Coin(4);
|
||||
|
|
|
@ -1,7 +1,63 @@
|
|||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use scale::{Encode, Decode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use sp_core::{
|
||||
H256,
|
||||
sr25519::{Public, Signature as RistrettoSignature},
|
||||
};
|
||||
|
||||
mod amount;
|
||||
pub use amount::*;
|
||||
|
||||
mod coins;
|
||||
pub use coins::*;
|
||||
|
||||
pub type PublicKey = Public;
|
||||
pub type SeraiAddress = PublicKey;
|
||||
pub type Signature = RistrettoSignature;
|
||||
|
||||
/// The type used to identify block numbers.
|
||||
// Doesn't re-export tendermint-machine's due to traits.
|
||||
#[derive(
|
||||
Clone, Copy, Default, PartialEq, Eq, Hash, Debug, Encode, Decode, MaxEncodedLen, TypeInfo,
|
||||
)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct BlockNumber(pub u32);
|
||||
impl From<u32> for BlockNumber {
|
||||
fn from(number: u32) -> BlockNumber {
|
||||
BlockNumber(number)
|
||||
}
|
||||
}
|
||||
|
||||
/// The type used to identify block hashes.
|
||||
// This may not be universally compatible
|
||||
// If a block exists with a hash which isn't 32-bytes, it can be hashed into a value with 32-bytes
|
||||
// This would require the processor to maintain a mapping of 32-byte IDs to actual hashes, which
|
||||
// would be fine
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct BlockHash(pub [u8; 32]);
|
||||
|
||||
impl AsRef<[u8]> for BlockHash {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for BlockHash {
|
||||
fn from(hash: [u8; 32]) -> BlockHash {
|
||||
BlockHash(hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<H256> for BlockHash {
|
||||
fn from(hash: H256) -> BlockHash {
|
||||
BlockHash(hash.into())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,8 +33,6 @@ sp-consensus = { git = "https://github.com/serai-dex/substrate" }
|
|||
|
||||
sp-tendermint = { path = "../primitives" }
|
||||
|
||||
sc-transaction-pool = { git = "https://github.com/serai-dex/substrate" }
|
||||
sc-executor = { git = "https://github.com/serai-dex/substrate" }
|
||||
sc-network-common = { git = "https://github.com/serai-dex/substrate" }
|
||||
sc-network = { git = "https://github.com/serai-dex/substrate" }
|
||||
sc-network-gossip = { git = "https://github.com/serai-dex/substrate" }
|
||||
|
|
|
@ -13,7 +13,7 @@ thiserror = "1"
|
|||
|
||||
log = "0.4"
|
||||
|
||||
parity-scale-codec = { version = "3.2", features = ["derive"] }
|
||||
parity-scale-codec = { version = "3", features = ["derive"] }
|
||||
|
||||
futures = "0.3"
|
||||
tokio = { version = "1", features = ["macros", "sync", "time", "rt"] }
|
||||
|
|
|
@ -17,14 +17,14 @@ pub mod pallet {
|
|||
}
|
||||
|
||||
#[pallet::genesis_config]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen)]
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
/// Bond requirement to join the initial validator set.
|
||||
/// Every participant at genesis will automatically be assumed to have this much bond.
|
||||
/// This bond cannot be withdrawn however as there's no stake behind it.
|
||||
pub bond: Amount,
|
||||
/// Amount of coins to spawn the network with in the initial validator set.
|
||||
pub coins: Coin,
|
||||
/// Coins to spawn the network with in the initial validator set.
|
||||
pub coins: Vec<Coin>,
|
||||
/// List of participants to place in the genesis set.
|
||||
pub participants: Vec<T::AccountId>,
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ pub mod pallet {
|
|||
#[cfg(feature = "std")]
|
||||
impl<T: Config> Default for GenesisConfig<T> {
|
||||
fn default() -> Self {
|
||||
GenesisConfig { bond: Amount(1), coins: Coin(0), participants: vec![] }
|
||||
GenesisConfig { bond: Amount(1), coins: vec![], participants: vec![] }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,11 +95,6 @@ pub mod pallet {
|
|||
#[pallet::genesis_build]
|
||||
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
let mut coins = Vec::new();
|
||||
for coin in 0 .. self.coins.0 {
|
||||
coins.push(Coin(coin));
|
||||
}
|
||||
|
||||
let mut participants = Vec::new();
|
||||
for participant in self.participants.clone() {
|
||||
participants.push((participant, self.bond));
|
||||
|
@ -109,7 +104,7 @@ pub mod pallet {
|
|||
ValidatorSetInstance(Session(0), ValidatorSetIndex(0)),
|
||||
Some(ValidatorSet {
|
||||
bond: self.bond,
|
||||
coins: BoundedVec::try_from(coins).unwrap(),
|
||||
coins: BoundedVec::try_from(self.coins.clone()).unwrap(),
|
||||
participants: BoundedVec::try_from(participants).unwrap(),
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -15,11 +15,8 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
|
||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
|
||||
[features]
|
||||
std = ["scale/std", "scale-info/std", "serde", "sp-core/std", "sp-std/std"]
|
||||
std = ["scale/std", "scale-info/std", "serde"]
|
||||
default = ["std"]
|
||||
|
|
Loading…
Reference in a new issue