mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-13 22:49:25 +00:00
Compare commits
8 Commits
c98d757c0f
...
next-polka
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee9b9778b5 | ||
|
|
5a3cf1f2be | ||
|
|
2fbe925c4d | ||
|
|
6aad496d86 | ||
|
|
d3464cfcb3 | ||
|
|
8dbea8452d | ||
|
|
f94b7ca50e | ||
|
|
5e39f9bc1e |
29
.github/actions/monero/action.yml
vendored
29
.github/actions/monero/action.yml
vendored
@@ -19,25 +19,32 @@ runs:
|
||||
|
||||
- name: Download the Monero Daemon
|
||||
if: steps.cache-monerod.outputs.cache-hit != 'true'
|
||||
# Calculates OS/ARCH to demonstrate it, yet then locks to linux-x64 due
|
||||
# to the contained folder not following the same naming scheme and
|
||||
# requiring further expansion not worth doing right now
|
||||
shell: bash
|
||||
run: |
|
||||
RUNNER_OS=${{ runner.os }}
|
||||
RUNNER_ARCH=${{ runner.arch }}
|
||||
OS=${{ runner.os }}
|
||||
ARCH=${{ runner.arch }}
|
||||
|
||||
RUNNER_OS=${RUNNER_OS,,}
|
||||
RUNNER_ARCH=${RUNNER_ARCH,,}
|
||||
OS=$(echo "$OS" | tr "[:upper:]" "[:lower:]")
|
||||
ARCH=$(echo "$ARCH" | tr "[:upper:]" "[:lower:]")
|
||||
|
||||
RUNNER_OS=linux
|
||||
RUNNER_ARCH=x64
|
||||
if [ "$OS" = "windows" ]; then
|
||||
OS=win
|
||||
echo "Windows is unsupported at this time"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$OS" = "macos" ]; then
|
||||
OS=mac
|
||||
fi
|
||||
if [ "$ARCH" = "arm64" ]; then
|
||||
ARCH=armv8
|
||||
fi
|
||||
|
||||
FILE=monero-$RUNNER_OS-$RUNNER_ARCH-${{ inputs.version }}.tar.bz2
|
||||
FILE=monero-$OS-$ARCH-${{ inputs.version }}.tar.bz2
|
||||
wget https://downloads.getmonero.org/cli/$FILE
|
||||
tar -xvf $FILE
|
||||
rm $FILE
|
||||
|
||||
sudo mv monero-x86_64-linux-gnu-${{ inputs.version }}/monerod /usr/bin/monerod
|
||||
sudo mv $(find . -name monerod) /usr/bin/monerod
|
||||
sudo chmod 777 /usr/bin/monerod
|
||||
sudo chmod +x /usr/bin/monerod
|
||||
|
||||
|
||||
2
.github/workflows/daily-deny.yml
vendored
2
.github/workflows/daily-deny.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0
|
||||
|
||||
- name: Install cargo deny
|
||||
run: cargo +1.91.1 install cargo-deny --version =0.18.8
|
||||
run: cargo +1.91.1 install cargo-deny --version =0.18.9
|
||||
|
||||
- name: Run cargo deny
|
||||
run: cargo deny -L error --all-features check --hide-inclusion-graph
|
||||
|
||||
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0
|
||||
|
||||
- name: Install cargo deny
|
||||
run: cargo +1.91.1 install cargo-deny --version =0.18.8
|
||||
run: cargo +1.91.1 install cargo-deny --version =0.18.9
|
||||
|
||||
- name: Run cargo deny
|
||||
run: cargo deny -L error --all-features check --hide-inclusion-graph
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
cache: false
|
||||
|
||||
- name: Run forge fmt
|
||||
run: FOUNDRY_FMT_SORT_INPUTS=false FOUNDRY_FMT_LINE_LENGTH=100 FOUNDRY_FMT_TAB_WIDTH=2 FOUNDRY_FMT_BRACKET_SPACING=true FOUNDRY_FMT_INT_TYPES=preserve forge fmt --check $(find . -iname "*.sol")
|
||||
run: FOUNDRY_FMT_SORT_INPUTS=false FOUNDRY_FMT_LINE_LENGTH=100 FOUNDRY_FMT_TAB_WIDTH=2 FOUNDRY_FMT_BRACKET_SPACING=true FOUNDRY_FMT_INT_TYPES=preserve forge fmt --check $(find . -name "*.sol")
|
||||
|
||||
machete:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -209,6 +209,6 @@ jobs:
|
||||
- name: shellcheck
|
||||
run: |
|
||||
sudo apt install -y shellcheck
|
||||
find . -iname "*.sh" | while read -r script; do
|
||||
find . -name "*.sh" | while read -r script; do
|
||||
shellcheck --enable=all --shell=sh --severity=info $script
|
||||
done
|
||||
|
||||
115
.github/workflows/stack-size.yml
vendored
115
.github/workflows/stack-size.yml
vendored
@@ -16,22 +16,99 @@ jobs:
|
||||
stack_size:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, ubuntu-24.04, ubuntu-22.04]
|
||||
os: [ubuntu-latest, ubuntu-24.04, ubuntu-22.04, macos-15-intel, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 6.0.0
|
||||
|
||||
- name: Download Monero
|
||||
uses: ./.github/actions/monero
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # 6.1.0
|
||||
with:
|
||||
go-version: stable
|
||||
|
||||
- name: Monero Daemon Cache
|
||||
id: cache-monerod
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # 4.2.4
|
||||
with:
|
||||
path: monerod
|
||||
key: stack-size-monerod
|
||||
|
||||
- name: Download the Monero Daemon
|
||||
if: steps.cache-monerod.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
# We explicitly download the Linux binary as this script executes over an ELF binary
|
||||
wget https://downloads.getmonero.org/cli/monero-linux-x64-v0.18.4.4.tar.bz2
|
||||
tar -xvf monero-linux-x64-v0.18.4.4.tar.bz2
|
||||
mv $(find . -name monerod) .
|
||||
|
||||
- name: Verify expected behavior
|
||||
shell: bash
|
||||
run: |
|
||||
cp /usr/bin/monerod monerod
|
||||
|
||||
STACK=$((8 * 1024 * 1024))
|
||||
|
||||
OS=${{ runner.os }}
|
||||
if [ "$OS" = "Linux" ]; then
|
||||
sudo apt update -y
|
||||
sudo apt install -y ksh bash dash zsh busybox posh mksh yash
|
||||
sudo ln -s "$(which busybox)" /usr/bin/ash
|
||||
sudo ln -s "$(which busybox)" /usr/bin/hush
|
||||
wget http://ftp.us.debian.org/debian/pool/main/g/gash/gash_0.3.1-1_amd64.deb
|
||||
sudo apt install ./gash_0.3.1-1_amd64.deb
|
||||
SHELLS="sh ksh bash dash zsh ash hush posh mksh lksh gash yash"
|
||||
fi
|
||||
if [ "$OS" = "macOS" ]; then
|
||||
brew install binutils # `readelf`
|
||||
|
||||
# `binutils` is not placed within the path, so find its
|
||||
# `readelf` bin and manually move it into our path
|
||||
HOMEBREW_ROOT_PATH=/opt/homebrew # Apple Silicon
|
||||
if [ $(uname -m) = "x86_64" ]; then HOMEBREW_ROOT_PATH=/usr/local; fi # Intel
|
||||
sudo cp $(find "$HOMEBREW_ROOT_PATH" -name readelf) /usr/local/bin/
|
||||
|
||||
# macOS has the benefit of packaging `oksh`, `osh`, and having distinct core tools
|
||||
# TODO: `posh` is packaged but doesn't work: https://github.com/serai-dex/serai/issues/703
|
||||
brew install ksh93 bash dash-shell zsh mksh oksh yash oils-for-unix
|
||||
SHELLS="sh ksh bash dash zsh mksh oksh yash osh"
|
||||
|
||||
# macOS also has the benefit of packaging (via MacPorts) `mrsh`,
|
||||
# which explicitly attempts to be be exactly POSIX, without any extensions.
|
||||
# We first have to install MacPorts, the easiest method being via source.
|
||||
curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.11.6.tar.bz2
|
||||
tar xf MacPorts-2.11.6.tar.bz2
|
||||
cd MacPorts-2.11.6
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
cd ..
|
||||
PATH=$PATH:/opt/local/bin
|
||||
sudo port -v selfupdate
|
||||
|
||||
# Now, we install `mrsh`
|
||||
# TODO: https://github.com/serai-dex/serai/issues/704
|
||||
# sudo port install mrsh
|
||||
# SHELLS="$SHELLS mrsh"
|
||||
fi
|
||||
|
||||
# Install shells available via `cargo`
|
||||
cargo install brush-shell
|
||||
SHELLS="$SHELLS brush"
|
||||
# We would also test with `nsh` here if not for https://github.com/nuta/nsh/issues/49
|
||||
# cargo install nsh
|
||||
# SHELLS="$SHELLS nsh"
|
||||
|
||||
# Install shells available via `go`
|
||||
# TODO: https://github.com/u-root/u-root/issues/3474
|
||||
# GOBIN=/usr/local/bin go install github.com/u-root/u-root/cmds/core/gosh@latest
|
||||
# SHELLS="$SHELLS gosh"
|
||||
|
||||
# Patch with `muslstack`
|
||||
cp monerod monerod-muslstack
|
||||
GOBIN=$(pwd) go install github.com/yaegashi/muslstack@d19cc5866abce3ca59dfc1666df7cc97097d0933
|
||||
./muslstack -s "$STACK" ./monerod-muslstack
|
||||
|
||||
# Patch with `chelf`, which only works on a Linux host (due to requiring `elf.h`)
|
||||
# TODO: Install the header on macOS so `chelf` may be used as the source of truth
|
||||
if [ "$OS" = "Linux" ]; then
|
||||
cp monerod monerod-chelf
|
||||
git clone https://github.com/Gottox/chelf
|
||||
cd chelf
|
||||
@@ -39,35 +116,31 @@ jobs:
|
||||
make
|
||||
./chelf -s "$STACK" ../monerod-chelf
|
||||
cd ..
|
||||
fi
|
||||
|
||||
cp monerod monerod-muslstack
|
||||
GOBIN=$(pwd) go install github.com/yaegashi/muslstack@d19cc5866abce3ca59dfc1666df7cc97097d0933
|
||||
./muslstack -s "$STACK" ./monerod-muslstack
|
||||
|
||||
sudo apt update -y
|
||||
sudo apt install -y ksh bash dash zsh busybox mksh posh yash
|
||||
sudo ln -s "$(which busybox)" /usr/bin/ash
|
||||
sudo ln -s "$(which busybox)" /usr/bin/hush
|
||||
cargo install brush-shell
|
||||
for shell in sh ksh bash dash zsh ash hush mksh lksh posh yash brush; do
|
||||
# Run our script with all installed shells
|
||||
for shell in $SHELLS; do
|
||||
echo "Executing \`$shell\`"
|
||||
cp monerod monerod-idss-$shell
|
||||
ln -s "$(which $shell)" sh
|
||||
./sh ./orchestration/increase_default_stack_size.sh monerod-idss-$shell
|
||||
rm ./sh
|
||||
done
|
||||
|
||||
# Verify they all had the same result
|
||||
sha256() {
|
||||
sha256sum "$1" | cut -d' ' -f1
|
||||
}
|
||||
CHELF=$(sha256 monerod-chelf)
|
||||
find . -iname "monerod-*" | while read -r bin; do
|
||||
CHELF=$(sha256 monerod-muslstack)
|
||||
find . -name "monerod-*" | while read -r bin; do
|
||||
BIN=$(sha256 "$bin")
|
||||
if [ ! "$CHELF" = "$BIN" ]; then
|
||||
echo "Different artifact between monerod-chelf ($CHELF) and $bin ($BIN)"
|
||||
echo "Different artifact between \`monerod-muslstack\` ($CHELF) and \`$bin\` ($BIN)"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Verify the integrity of the result
|
||||
read_stack() {
|
||||
STACK_INFO=$(readelf "$1" -l | grep STACK -A1)
|
||||
MEMSZ=$(printf "%s\n" "$STACK_INFO" | tail -n1 | sed -E s/^[[:space:]]*//g | cut -f2 -d' ')
|
||||
@@ -79,14 +152,14 @@ jobs:
|
||||
exit 2
|
||||
fi
|
||||
|
||||
UPDATED_STACK=$(read_stack monerod-chelf)
|
||||
UPDATED_STACK=$(read_stack monerod-muslstack)
|
||||
if [ "$UPDATED_STACK" -ne "$STACK" ]; then
|
||||
echo "Updated \`PT_GNU_STACK\` ($UPDATED_STACK) wasn't 8 MB ($STACK)"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
# Only one byte should be different due to the bit pattern of 8 MB
|
||||
BYTES_DIFFERENT=$(cmp -l monerod monerod-chelf | wc -l || true)
|
||||
BYTES_DIFFERENT=$(cmp -l monerod monerod-muslstack | wc -l || true)
|
||||
if [ "$BYTES_DIFFERENT" -ne 1 ]; then
|
||||
echo "More than one byte was different between the two binaries"
|
||||
exit 4
|
||||
|
||||
14
Cargo.lock
generated
14
Cargo.lock
generated
@@ -92,9 +92,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "alloy-chains"
|
||||
version = "0.2.21"
|
||||
version = "0.2.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9ebac8ff9c2f07667e1803dc777304337e160ce5153335beb45e8ec0751808"
|
||||
checksum = "35d744058a9daa51a8cf22a3009607498fcf82d3cf4c5444dd8056cdf651f471"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"num_enum",
|
||||
@@ -4148,8 +4148,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "git+https://github.com/rust-lang-nursery/lazy-static.rs?rev=5735630d46572f1e5377c8f2ba0f79d18f53b10c#5735630d46572f1e5377c8f2ba0f79d18f53b10c"
|
||||
version = "1.99.0"
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
@@ -4321,9 +4320,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libp2p-identity"
|
||||
version = "0.2.12"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3104e13b51e4711ff5738caa1fb54467c8604c2e94d607e27745bcf709068774"
|
||||
checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"ed25519-dalek",
|
||||
@@ -8433,6 +8432,7 @@ name = "serai-in-instructions-pallet"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"borsh",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"parity-scale-codec",
|
||||
@@ -8441,7 +8441,9 @@ dependencies = [
|
||||
"serai-core-pallet",
|
||||
"serai-dex-pallet",
|
||||
"serai-genesis-liquidity-pallet",
|
||||
"serai-signals-pallet",
|
||||
"serai-validator-sets-pallet",
|
||||
"sp-application-crypto",
|
||||
"sp-core",
|
||||
]
|
||||
|
||||
|
||||
@@ -200,8 +200,9 @@ dalek-ff-group = { path = "patches/dalek-ff-group" }
|
||||
minimal-ed448 = { path = "crypto/ed448" }
|
||||
modular-frost = { path = "crypto/frost" }
|
||||
|
||||
# Patch due to `std` now including the required functionality
|
||||
is_terminal_polyfill = { path = "./patches/is_terminal_polyfill" }
|
||||
# Patches due to `std` now including the required functionality
|
||||
is_terminal_polyfill = { path = "patches/is_terminal_polyfill" }
|
||||
lazy_static = { path = "patches/lazy_static" }
|
||||
# This has a non-deprecated `std` alternative since Rust's 2024 edition
|
||||
home = { path = "patches/home" }
|
||||
|
||||
@@ -209,9 +210,6 @@ home = { path = "patches/home" }
|
||||
darling = { path = "patches/darling" }
|
||||
thiserror = { path = "patches/thiserror" }
|
||||
|
||||
# https://github.com/rust-lang-nursery/lazy-static.rs/issues/201
|
||||
lazy_static = { git = "https://github.com/rust-lang-nursery/lazy-static.rs", rev = "5735630d46572f1e5377c8f2ba0f79d18f53b10c" }
|
||||
|
||||
# directories-next was created because directories was unmaintained
|
||||
# directories-next is now unmaintained while directories is maintained
|
||||
# The directories author pulls in ridiculously pointless crates and prefers
|
||||
|
||||
@@ -147,7 +147,6 @@ unknown-registry = "deny"
|
||||
unknown-git = "deny"
|
||||
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
|
||||
allow-git = [
|
||||
"https://github.com/rust-lang-nursery/lazy-static.rs",
|
||||
"https://github.com/kayabaNerve/elliptic-curves",
|
||||
"https://github.com/monero-oxide/monero-oxide",
|
||||
"https://github.com/serai-dex/patch-polkadot-sdk",
|
||||
|
||||
@@ -19,7 +19,7 @@ RUN wget -4 https://bitcoincore.org/bin/bitcoin-core-${BITCOIN_VERSION}/SHA256SU
|
||||
# Verify all sigs and check for a valid signature from laanwj -- 71A3
|
||||
RUN git clone https://github.com/bitcoin-core/guix.sigs && \
|
||||
cd guix.sigs/builder-keys && \
|
||||
find . -iname '*.gpg' -exec gpg --import {} \; && \
|
||||
find . -name '*.gpg' -exec gpg --import {} \; && \
|
||||
gpg --verify --status-fd 1 --verify ../../SHA256SUMS.asc ../../SHA256SUMS | grep "^\[GNUPG:\] VALIDSIG.*71A3B16735405025D447E8F274810B012346C9A6"
|
||||
|
||||
RUN grep bitcoin-${BITCOIN_VERSION}-$(uname -m)-linux-gnu.tar.gz SHA256SUMS | sha256sum -c
|
||||
|
||||
16
patches/lazy_static/Cargo.toml
Normal file
16
patches/lazy_static/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "lazy_static"
|
||||
version = "1.99.0"
|
||||
description = "`lazy_static` which patches to `std::sync::LazyLock`"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/patches/lazy_static"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = []
|
||||
edition = "2021"
|
||||
rust-version = "1.80"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[workspace]
|
||||
14
patches/lazy_static/src/lib.rs
Normal file
14
patches/lazy_static/src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
#[macro_export]
|
||||
macro_rules! lazy_static {
|
||||
($($(#[$attr: meta])* $vis: vis static ref $name: ident: $type: ty = $value: expr;)*) => {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
$vis static $name: std::sync::LazyLock<$type> = std::sync::LazyLock::new(|| $value);
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
/// Explicitly initialize a static declared with [`lazy_static`].
|
||||
pub fn initialize<T, F: Fn() -> T>(lazy: &std::sync::LazyLock<T, F>) {
|
||||
std::sync::LazyLock::force(lazy);
|
||||
}
|
||||
@@ -1,9 +1,15 @@
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
use serai_primitives::{
|
||||
BlockHash, network_id::ExternalNetworkId, validator_sets::Session, instructions::SignedBatch,
|
||||
BlockHash, network_id::ExternalNetworkId, validator_sets::Session, address::SeraiAddress,
|
||||
instructions::SignedBatch,
|
||||
};
|
||||
|
||||
/// The address used for executing `InInstruction`s.
|
||||
pub fn address() -> SeraiAddress {
|
||||
SeraiAddress::system(borsh::to_vec(b"InInstructions").unwrap())
|
||||
}
|
||||
|
||||
/// A call to `InInstruction`s.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Call {
|
||||
|
||||
@@ -21,8 +21,10 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||
borsh = { version = "1", default-features = false }
|
||||
|
||||
sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
|
||||
sp-application-crypto = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
|
||||
|
||||
frame-system = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
|
||||
frame-support = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
|
||||
@@ -32,14 +34,17 @@ serai-abi = { path = "../abi", default-features = false, features = ["substrate"
|
||||
serai-core-pallet = { path = "../core", default-features = false }
|
||||
serai-coins-pallet = { path = "../coins", default-features = false }
|
||||
serai-validator-sets-pallet = { path = "../validator-sets", default-features = false }
|
||||
serai-signals-pallet = { path = "../signals", default-features = false }
|
||||
serai-dex-pallet = { path = "../dex", default-features = false }
|
||||
serai-genesis-liquidity-pallet = { path = "../genesis-liquidity", default-features = false }
|
||||
|
||||
[features]
|
||||
std = [
|
||||
"scale/std",
|
||||
"borsh/std",
|
||||
|
||||
"sp-core/std",
|
||||
"sp-application-crypto/std",
|
||||
|
||||
"frame-system/std",
|
||||
"frame-support/std",
|
||||
@@ -49,6 +54,7 @@ std = [
|
||||
"serai-core-pallet/std",
|
||||
"serai-coins-pallet/std",
|
||||
"serai-validator-sets-pallet/std",
|
||||
"serai-signals-pallet/std",
|
||||
"serai-dex-pallet/std",
|
||||
"serai-genesis-liquidity-pallet/std",
|
||||
]
|
||||
@@ -61,6 +67,7 @@ try-runtime = [
|
||||
"serai-core-pallet/try-runtime",
|
||||
"serai-coins-pallet/try-runtime",
|
||||
"serai-validator-sets-pallet/try-runtime",
|
||||
"serai-signals-pallet/try-runtime",
|
||||
"serai-dex-pallet/try-runtime",
|
||||
"serai-genesis-liquidity-pallet/try-runtime",
|
||||
]
|
||||
@@ -72,6 +79,7 @@ runtime-benchmarks = [
|
||||
"serai-core-pallet/runtime-benchmarks",
|
||||
"serai-coins-pallet/runtime-benchmarks",
|
||||
"serai-validator-sets-pallet/runtime-benchmarks",
|
||||
"serai-signals-pallet/runtime-benchmarks",
|
||||
"serai-dex-pallet/runtime-benchmarks",
|
||||
"serai-genesis-liquidity-pallet/runtime-benchmarks",
|
||||
]
|
||||
|
||||
@@ -7,8 +7,11 @@ extern crate alloc;
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[frame_support::pallet]
|
||||
mod pallet {
|
||||
use sp_core::sr25519::Public;
|
||||
use sp_application_crypto::RuntimePublic;
|
||||
|
||||
use frame_support::{pallet_prelude::*, dispatch::RawOrigin};
|
||||
use frame_system::pallet_prelude::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
|
||||
use serai_abi::{primitives::prelude::*, in_instructions::Event};
|
||||
|
||||
@@ -26,6 +29,7 @@ mod pallet {
|
||||
+ serai_core_pallet::Config
|
||||
+ serai_coins_pallet::Config<serai_coins_pallet::CoinsInstance>
|
||||
+ serai_validator_sets_pallet::Config
|
||||
+ serai_signals_pallet::Config
|
||||
+ serai_coins_pallet::Config<serai_coins_pallet::LiquidityTokensInstance>
|
||||
+ serai_dex_pallet::Config
|
||||
+ serai_genesis_liquidity_pallet::Config
|
||||
@@ -36,6 +40,18 @@ mod pallet {
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {}
|
||||
|
||||
/// The block the last batch was published during.
|
||||
///
|
||||
/// This is used to restrict publication of batches to once-per-block, limiting the impact of a
|
||||
/// compromised publisher from bloating the Serai blockchain with spam.
|
||||
#[pallet::storage]
|
||||
type BlockOfLastBatch<T: Config> =
|
||||
StorageMap<_, Identity, ExternalNetworkId, BlockNumberFor<T>, OptionQuery>;
|
||||
|
||||
/// The ID of the last batch which was published.
|
||||
#[pallet::storage]
|
||||
type LastBatch<T: Config> = StorageMap<_, Identity, ExternalNetworkId, u32, OptionQuery>;
|
||||
|
||||
/// The Pallet struct.
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
@@ -44,6 +60,127 @@ mod pallet {
|
||||
fn emit_event(event: Event) {
|
||||
Core::<T>::emit_event(event)
|
||||
}
|
||||
|
||||
/// Execute an `InInstructionWithBalance`.
|
||||
///
|
||||
/// We execute this within a database layer to ensure it's atomic, executing entirely or not at
|
||||
/// all.
|
||||
#[frame_support::transactional]
|
||||
fn execute(instruction: InInstructionWithBalance) -> DispatchResult {
|
||||
let InInstructionWithBalance { instruction, balance: external_balance } = instruction;
|
||||
let balance = <Balance as From<ExternalBalance>>::from(external_balance);
|
||||
|
||||
// Mint the balance to ourself
|
||||
let address = serai_abi::in_instructions::address();
|
||||
Coins::<T>::mint(address.into(), balance)?;
|
||||
|
||||
match instruction {
|
||||
InInstruction::GenesisLiquidity(address) => {
|
||||
serai_genesis_liquidity_pallet::Pallet::<T>::add_liquidity(address, external_balance)?;
|
||||
}
|
||||
InInstruction::SwapToStakedSri { validator, minimum } => todo!("TODO"),
|
||||
InInstruction::TransferWithSwap { to, maximum_to_swap, sri } => {
|
||||
serai_dex_pallet::Pallet::<T>::swap_for(
|
||||
RawOrigin::Signed(address.into()).into(),
|
||||
Balance { coin: Coin::Serai, amount: sri },
|
||||
Balance { coin: balance.coin, amount: maximum_to_swap },
|
||||
)?;
|
||||
|
||||
Coins::<T>::transfer_fn(
|
||||
address.into(),
|
||||
to.into(),
|
||||
Balance {
|
||||
coin: balance.coin,
|
||||
amount: Coins::<T>::balance(Public::from(address), balance.coin),
|
||||
},
|
||||
)?;
|
||||
Coins::<T>::transfer_fn(
|
||||
address.into(),
|
||||
to.into(),
|
||||
Balance {
|
||||
coin: Coin::Serai,
|
||||
amount: Coins::<T>::balance(Public::from(address), Coin::Serai),
|
||||
},
|
||||
)?;
|
||||
}
|
||||
InInstruction::Transfer { to } => {
|
||||
Coins::<T>::transfer_fn(address.into(), to.into(), balance)?;
|
||||
}
|
||||
InInstruction::SwapAndAddLiquidity {
|
||||
address: destination,
|
||||
coin,
|
||||
sri_minimum,
|
||||
sri_for_fees,
|
||||
} => {
|
||||
let external_coin = external_balance.coin;
|
||||
serai_dex_pallet::Pallet::<T>::swap(
|
||||
RawOrigin::Signed(address.into()).into(),
|
||||
Balance {
|
||||
coin: Coin::External(external_coin),
|
||||
amount: (balance.amount - coin).ok_or(serai_dex_pallet::Error::<T>::Underflow)?,
|
||||
},
|
||||
Balance {
|
||||
coin: Coin::Serai,
|
||||
amount: (sri_minimum + sri_for_fees).ok_or(serai_dex_pallet::Error::<T>::Overflow)?,
|
||||
},
|
||||
)?;
|
||||
|
||||
let sri_intended = (Coins::<T>::balance(Public::from(address), Coin::Serai) -
|
||||
sri_for_fees)
|
||||
.expect("swapped to amount sufficient for minimum, fees, but received less than fees?");
|
||||
let coin_intended = coin;
|
||||
let coin_minimum = coin;
|
||||
serai_dex_pallet::Pallet::<T>::add_liquidity(
|
||||
RawOrigin::Signed(address.into()).into(),
|
||||
external_coin,
|
||||
sri_intended,
|
||||
coin_intended,
|
||||
sri_minimum,
|
||||
coin_minimum,
|
||||
)?;
|
||||
|
||||
// Transfer the rest, which will be more than the amount requested for fees, to the
|
||||
// destination
|
||||
Coins::<T>::transfer_fn(
|
||||
address.into(),
|
||||
destination.into(),
|
||||
Balance {
|
||||
coin: Coin::Serai,
|
||||
amount: Coins::<T>::balance(Public::from(address), Coin::Serai),
|
||||
},
|
||||
)?;
|
||||
}
|
||||
InInstruction::Swap { address: destination, minimum_to_receive } => {
|
||||
serai_dex_pallet::Pallet::<T>::swap(
|
||||
RawOrigin::Signed(address.into()).into(),
|
||||
balance,
|
||||
minimum_to_receive,
|
||||
)?;
|
||||
|
||||
let coin = minimum_to_receive.coin;
|
||||
let received_amount = Coins::<T>::balance(Public::from(address), coin);
|
||||
let received = Balance { coin, amount: received_amount };
|
||||
Coins::<T>::transfer_fn(address.into(), destination.into(), received)?;
|
||||
}
|
||||
InInstruction::SwapOut { instruction, minimum_to_receive } => {
|
||||
serai_dex_pallet::Pallet::<T>::swap(
|
||||
RawOrigin::Signed(address.into()).into(),
|
||||
balance,
|
||||
minimum_to_receive.into(),
|
||||
)?;
|
||||
|
||||
let coin = minimum_to_receive.coin;
|
||||
let received_amount = Coins::<T>::balance(Public::from(address), Coin::from(coin));
|
||||
let received = ExternalBalance { coin, amount: received_amount };
|
||||
Coins::<T>::burn_with_instruction(
|
||||
RawOrigin::Signed(address.into()).into(),
|
||||
OutInstructionWithBalance { instruction, balance: received },
|
||||
)?;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
@@ -52,7 +189,143 @@ mod pallet {
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
||||
pub fn execute_batch(origin: OriginFor<T>, batch: SignedBatch) -> DispatchResult {
|
||||
todo!("TODO")
|
||||
let batch = batch.batch;
|
||||
let network = batch.network();
|
||||
|
||||
let mut in_instruction_results = bitvec::vec::BitVec::new();
|
||||
for instruction in batch.instructions() {
|
||||
in_instruction_results.push(Self::execute(instruction.clone()).is_ok());
|
||||
}
|
||||
|
||||
// The publishing session is always the current session
|
||||
let publishing_session =
|
||||
serai_validator_sets_pallet::Pallet::<T>::current_session(NetworkId::from(network))
|
||||
.expect("`SignedBatch` for a network without a session was validated");
|
||||
|
||||
Self::emit_event(Event::Batch {
|
||||
network,
|
||||
publishing_session,
|
||||
id: batch.id(),
|
||||
external_network_block_hash: batch.external_network_block_hash(),
|
||||
in_instructions_hash: sp_core::blake2_256(&borsh::to_vec(batch.instructions()).unwrap()),
|
||||
in_instruction_results,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::validate_unsigned]
|
||||
impl<T: Config> ValidateUnsigned for Pallet<T> {
|
||||
type Call = Call<T>;
|
||||
|
||||
fn validate_unsigned(_: TransactionSource, call: &Self::Call) -> TransactionValidity {
|
||||
let batch = match call {
|
||||
Call::execute_batch { batch } => batch,
|
||||
Call::__Ignore(_, _) => Err(InvalidTransaction::Call)?,
|
||||
};
|
||||
|
||||
let network = batch.batch.network();
|
||||
|
||||
// Verify the network isn't halted
|
||||
if serai_signals_pallet::Pallet::<T>::halted(network) {
|
||||
Err(InvalidTransaction::Custom(1))?;
|
||||
}
|
||||
|
||||
// Verify the `Batch`'s signature
|
||||
let mut signed_by_latest_decided_session = false;
|
||||
{
|
||||
let message = batch.batch.publish_batch_message();
|
||||
let signed_by_session = |session| {
|
||||
let Some(key) =
|
||||
serai_validator_sets_pallet::Pallet::<T>::oraclization_key(ExternalValidatorSet {
|
||||
network,
|
||||
session,
|
||||
})
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
key.verify(&message, &batch.signature.into())
|
||||
};
|
||||
let Some(current_session) =
|
||||
serai_validator_sets_pallet::Pallet::<T>::current_session(NetworkId::from(network))
|
||||
else {
|
||||
Err(InvalidTransaction::BadProof)?
|
||||
};
|
||||
if !signed_by_session(current_session) {
|
||||
let latest_decided_session =
|
||||
serai_validator_sets_pallet::Pallet::<T>::latest_decided_session(NetworkId::from(
|
||||
network,
|
||||
))
|
||||
.expect("current session yet never one decided?");
|
||||
if !signed_by_session(latest_decided_session) {
|
||||
Err(InvalidTransaction::BadProof)?;
|
||||
}
|
||||
signed_by_latest_decided_session = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify every coin with the `Batch` corresponds to this network
|
||||
for instruction in batch.batch.instructions() {
|
||||
if instruction.balance.coin.network() != network {
|
||||
Err(InvalidTransaction::Custom(2))?;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify this is the first `Batch` for this block
|
||||
let current_block_number = frame_system::Pallet::<T>::block_number();
|
||||
if BlockOfLastBatch::<T>::get(network) == Some(current_block_number) {
|
||||
// This transaction is valid in the future, the next block, but not now
|
||||
Err(InvalidTransaction::Future)?;
|
||||
}
|
||||
|
||||
// Verify this `Batch` descends immediately from the prior `Batch`
|
||||
let required_last_batch = batch.batch.id().checked_sub(1);
|
||||
{
|
||||
let last_batch = LastBatch::<T>::get(network);
|
||||
if last_batch < required_last_batch {
|
||||
Err(InvalidTransaction::Future)?;
|
||||
}
|
||||
if last_batch > required_last_batch {
|
||||
Err(InvalidTransaction::Stale)?;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Set the metadata fields as necessary for further batches to be verified. While this is
|
||||
mutating the state within the verification function, it's necessary for the verification of
|
||||
the following transactions.
|
||||
|
||||
Additionally, we know these state changes occur as tests verify we can publish `Batch`es
|
||||
and have the `LastBatch` field be incremented.
|
||||
*/
|
||||
BlockOfLastBatch::<T>::set(network, Some(current_block_number));
|
||||
LastBatch::<T>::set(network, Some(batch.batch.id()));
|
||||
if signed_by_latest_decided_session {
|
||||
// Because the latest decided session, which is not the current session, has taken over
|
||||
// for publishing `Batch`es, it has agreed to become the current session
|
||||
serai_validator_sets_pallet::Pallet::<T>::accept_handover(network);
|
||||
}
|
||||
|
||||
let mut builder = ValidTransaction::with_tag_prefix("InInstructions");
|
||||
if let Some(required_last_batch) = required_last_batch {
|
||||
// TODO: Should this replace the DB mutations present within this verification function?
|
||||
builder = builder.and_requires((network, required_last_batch));
|
||||
}
|
||||
builder
|
||||
.and_provides((network, batch.batch.id()))
|
||||
// Set a 10 block longevity, though this should be included in the next block
|
||||
.longevity(10)
|
||||
.propagate(true)
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Explicitly provide a `pre_dispatch` which calls `validate_unsigned`.
|
||||
///
|
||||
/// This is reasonably assumed, and the current provided implementation, but not guaranteed by
|
||||
/// the documentation.
|
||||
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
|
||||
Self::validate_unsigned(TransactionSource::InBlock, call).map(|_| ())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,7 +433,7 @@ pub fn new_full(mut config: Configuration) -> Result<TaskManager, ServiceError>
|
||||
grandpa::run_grandpa_voter(grandpa::GrandpaParams {
|
||||
config: grandpa::Config {
|
||||
gossip_duration: std::time::Duration::from_millis(333),
|
||||
justification_generation_period: 512,
|
||||
justification_generation_period: 1,
|
||||
name: Some(name),
|
||||
observer_enabled: false,
|
||||
keystore: if role.is_authority() { Some(keystore) } else { None },
|
||||
|
||||
@@ -10,15 +10,6 @@ use crate::{
|
||||
mod batch;
|
||||
pub use batch::*;
|
||||
|
||||
/// The destination for coins.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Destination {
|
||||
/// The Serai address to transfer the coins to.
|
||||
Serai(SeraiAddress),
|
||||
/// Burn the coins with the included `OutInstruction`.
|
||||
Burn(OutInstruction),
|
||||
}
|
||||
|
||||
/// An instruction on how to handle coins in.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
||||
pub enum InInstruction {
|
||||
@@ -33,10 +24,10 @@ pub enum InInstruction {
|
||||
},
|
||||
/// Transfer the coins to a Serai address, swapping some for SRI.
|
||||
TransferWithSwap {
|
||||
/// The Serai address to transfer the coins to, after swapping some.
|
||||
/// The Serai address to transfer the coins to.
|
||||
to: SeraiAddress,
|
||||
/// The maximum amount of coins to swap for the intended amount of SRI.
|
||||
maximum_swap: Amount,
|
||||
maximum_to_swap: Amount,
|
||||
/// The SRI amount to swap some of the coins for.
|
||||
sri: Amount,
|
||||
},
|
||||
@@ -47,23 +38,28 @@ pub enum InInstruction {
|
||||
},
|
||||
/// Swap part of the coins to SRI and add the coins as liquidity.
|
||||
SwapAndAddLiquidity {
|
||||
/// The owner to-be of the added liquidity.
|
||||
owner: SeraiAddress,
|
||||
/// The amount of SRI to add within the liquidity position.
|
||||
sri: Amount,
|
||||
/// The minimum amount of the coin to add as liquidity.
|
||||
minimum_coin: Amount,
|
||||
/// The recipient to-be of the added liquidity.
|
||||
address: SeraiAddress,
|
||||
/// The amount of the coin to add within the liquidity position.
|
||||
coin: Amount,
|
||||
/// The minimum amount of SRI to add as liquidity.
|
||||
sri_minimum: Amount,
|
||||
/// The amount of SRI to swap to and send to the owner to-be to pay for transactions on Serai.
|
||||
sri_for_fees: Amount,
|
||||
},
|
||||
/// Swap the coins.
|
||||
Swap {
|
||||
/// The minimum balance to receive.
|
||||
minimum_out: Balance,
|
||||
/// The destination to transfer the balance to.
|
||||
///
|
||||
/// If `Destination::Burn`, the balance out will be burnt with the included `OutInstruction`.
|
||||
destination: Destination,
|
||||
/// The destination to received the coins swapped to with.
|
||||
address: SeraiAddress,
|
||||
/// The minimum balance to receive from the swap.
|
||||
minimum_to_receive: Balance,
|
||||
},
|
||||
/// Swap the coins, burning them with the included `OutInstruction`.
|
||||
SwapOut {
|
||||
/// The instruction to burn the coins swapped to with.
|
||||
instruction: OutInstruction,
|
||||
/// The minimum balance to receive from the swap.
|
||||
minimum_to_receive: ExternalBalance,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -234,6 +234,11 @@ pub mod pallet {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check if an external network was halted.
|
||||
pub fn halted(network: ExternalNetworkId) -> bool {
|
||||
Halted::<T>::contains_key(network)
|
||||
}
|
||||
}
|
||||
|
||||
/// An error from the `signals` pallet.
|
||||
|
||||
@@ -345,6 +345,14 @@ mod pallet {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Have the latest decided session become the current session.
|
||||
///
|
||||
/// This is restricted to `ExternalNetworkId` as this process happens internally for
|
||||
/// `NetworkId::Serai`.
|
||||
pub fn accept_handover(network: ExternalNetworkId) {
|
||||
Abstractions::<T>::accept_handover(network.into());
|
||||
}
|
||||
|
||||
/* TODO
|
||||
pub fn distribute_block_rewards(
|
||||
network: NetworkId,
|
||||
|
||||
Reference in New Issue
Block a user