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:
Luke Parker 2023-01-20 11:00:18 -05:00 committed by GitHub
parent e13cf52c49
commit 8ca90e7905
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 1613 additions and 403 deletions

View file

@ -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 &

View file

@ -1 +1 @@
nightly-2022-12-01
nightly-2023-01-16

View file

@ -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
View file

@ -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"

View file

@ -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" }

View file

@ -13,7 +13,6 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
hex-literal = "0.3"
thiserror = "1"
rand_core = "0.6"

View file

@ -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"]

View file

@ -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,

View file

@ -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();

View file

@ -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()));

View file

@ -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]]

View file

@ -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

View file

@ -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

View file

@ -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`.

View file

@ -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 |

View file

@ -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"

View file

@ -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;

View 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
View 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));
}

View 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" }

View 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/>.

View 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()?))))
}
}

View 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"]

View 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/>.

View 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::*;

View 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"]

View 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.

View 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,
}

View 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::*;

View 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 }>>>,
}

View 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!(),
})
}
}

View file

@ -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]

View file

@ -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"),
],
)
},

View file

@ -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},

View file

@ -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,
(),

View file

@ -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>,
{

View file

@ -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(),))
}
}

View file

@ -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",

View file

@ -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)
}
}

View 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"

View 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/>.

View 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)))
}
}

View 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())
}
}

View 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;
}
)*
}
}

View 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;
}
}
);

View file

@ -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"]

View file

@ -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);

View file

@ -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);

View file

@ -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())
}
}

View file

@ -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" }

View file

@ -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"] }

View file

@ -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(),
}),
);

View file

@ -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"]