mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 20:29:23 +00:00
Merge branch 'develop' into next
This is an initial resolution of conflicts which does not work.
This commit is contained in:
5
.github/workflows/monero-tests.yaml
vendored
5
.github/workflows/monero-tests.yaml
vendored
@@ -39,9 +39,6 @@ jobs:
|
|||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-simple-request-rpc --lib
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-simple-request-rpc --lib
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-address --lib
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-address --lib
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet --lib
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet --lib
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-seed --lib
|
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package polyseed --lib
|
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet-util --lib
|
|
||||||
|
|
||||||
# Doesn't run unit tests with features as the tests workflow will
|
# Doesn't run unit tests with features as the tests workflow will
|
||||||
|
|
||||||
@@ -65,7 +62,6 @@ jobs:
|
|||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-serai --test '*'
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-serai --test '*'
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-simple-request-rpc --test '*'
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-simple-request-rpc --test '*'
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet --test '*'
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet --test '*'
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet-util --test '*'
|
|
||||||
|
|
||||||
- name: Run Integration Tests
|
- name: Run Integration Tests
|
||||||
# Don't run if the the tests workflow also will
|
# Don't run if the the tests workflow also will
|
||||||
@@ -74,4 +70,3 @@ jobs:
|
|||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-serai --all-features --test '*'
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-serai --all-features --test '*'
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-simple-request-rpc --test '*'
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-simple-request-rpc --test '*'
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet --all-features --test '*'
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet --all-features --test '*'
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --package monero-wallet-util --all-features --test '*'
|
|
||||||
|
|||||||
3
.github/workflows/networks-tests.yml
vendored
3
.github/workflows/networks-tests.yml
vendored
@@ -46,7 +46,4 @@ jobs:
|
|||||||
-p monero-simple-request-rpc \
|
-p monero-simple-request-rpc \
|
||||||
-p monero-address \
|
-p monero-address \
|
||||||
-p monero-wallet \
|
-p monero-wallet \
|
||||||
-p monero-seed \
|
|
||||||
-p polyseed \
|
|
||||||
-p monero-wallet-util \
|
|
||||||
-p monero-serai-verify-chain
|
-p monero-serai-verify-chain
|
||||||
|
|||||||
37
.github/workflows/pages.yml
vendored
37
.github/workflows/pages.yml
vendored
@@ -1,6 +1,7 @@
|
|||||||
# MIT License
|
# MIT License
|
||||||
#
|
#
|
||||||
# Copyright (c) 2022 just-the-docs
|
# Copyright (c) 2022 just-the-docs
|
||||||
|
# Copyright (c) 2022-2024 Luke Parker
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -20,31 +21,21 @@
|
|||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
# SOFTWARE.
|
# SOFTWARE.
|
||||||
|
|
||||||
# This workflow uses actions that are not certified by GitHub.
|
name: Deploy Rust docs and Jekyll site to Pages
|
||||||
# They are provided by a third-party and are governed by
|
|
||||||
# separate terms of service, privacy policy, and support
|
|
||||||
# documentation.
|
|
||||||
|
|
||||||
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
|
|
||||||
name: Deploy Jekyll site to Pages
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "develop"
|
- "develop"
|
||||||
paths:
|
|
||||||
- "docs/**"
|
|
||||||
|
|
||||||
# Allows you to run this workflow manually from the Actions tab
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
pages: write
|
pages: write
|
||||||
id-token: write
|
id-token: write
|
||||||
|
|
||||||
# Allow one concurrent deployment
|
# Only allow one concurrent deployment
|
||||||
concurrency:
|
concurrency:
|
||||||
group: "pages"
|
group: "pages"
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
@@ -53,9 +44,6 @@ jobs:
|
|||||||
# Build job
|
# Build job
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
working-directory: docs
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -69,11 +57,24 @@ jobs:
|
|||||||
id: pages
|
id: pages
|
||||||
uses: actions/configure-pages@v3
|
uses: actions/configure-pages@v3
|
||||||
- name: Build with Jekyll
|
- name: Build with Jekyll
|
||||||
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
|
run: cd ${{ github.workspace }}/docs && bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
|
||||||
env:
|
env:
|
||||||
JEKYLL_ENV: production
|
JEKYLL_ENV: production
|
||||||
|
|
||||||
|
- name: Get nightly version to use
|
||||||
|
id: nightly
|
||||||
|
shell: bash
|
||||||
|
run: echo "version=$(cat .github/nightly-version)" >> $GITHUB_OUTPUT
|
||||||
|
- name: Build Dependencies
|
||||||
|
uses: ./.github/actions/build-dependencies
|
||||||
|
- name: Buld Rust docs
|
||||||
|
run: |
|
||||||
|
rustup toolchain install ${{ steps.nightly.outputs.version }} --profile minimal -t wasm32-unknown-unknown -c rust-docs
|
||||||
|
RUSTDOCFLAGS="--cfg docsrs" cargo +${{ steps.nightly.outputs.version }} doc --workspace --all-features
|
||||||
|
mv target/doc docs/_site/rust
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-pages-artifact@v1
|
uses: actions/upload-pages-artifact@v3
|
||||||
with:
|
with:
|
||||||
path: "docs/_site/"
|
path: "docs/_site/"
|
||||||
|
|
||||||
@@ -87,4 +88,4 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v2
|
uses: actions/deploy-pages@v4
|
||||||
|
|||||||
@@ -64,9 +64,6 @@ members = [
|
|||||||
"networks/monero/rpc/simple-request",
|
"networks/monero/rpc/simple-request",
|
||||||
"networks/monero/wallet/address",
|
"networks/monero/wallet/address",
|
||||||
"networks/monero/wallet",
|
"networks/monero/wallet",
|
||||||
"networks/monero/wallet/seed",
|
|
||||||
"networks/monero/wallet/polyseed",
|
|
||||||
"networks/monero/wallet/util",
|
|
||||||
"networks/monero/verify-chain",
|
"networks/monero/verify-chain",
|
||||||
|
|
||||||
"message-queue",
|
"message-queue",
|
||||||
@@ -194,6 +191,9 @@ parking_lot = { path = "patches/parking_lot" }
|
|||||||
zstd = { path = "patches/zstd" }
|
zstd = { path = "patches/zstd" }
|
||||||
# Needed for WAL compression
|
# Needed for WAL compression
|
||||||
rocksdb = { path = "patches/rocksdb" }
|
rocksdb = { path = "patches/rocksdb" }
|
||||||
|
# 1.0.1 was yanked due to a breaking change (an extra field)
|
||||||
|
# 2.0 has fewer dependencies and still works within our tree
|
||||||
|
tiny-bip39 = { path = "patches/tiny-bip39" }
|
||||||
|
|
||||||
# is-terminal now has an std-based solution with an equivalent API
|
# is-terminal now has an std-based solution with an equivalent API
|
||||||
is-terminal = { path = "patches/is-terminal" }
|
is-terminal = { path = "patches/is-terminal" }
|
||||||
@@ -208,9 +208,6 @@ matches = { path = "patches/matches" }
|
|||||||
option-ext = { path = "patches/option-ext" }
|
option-ext = { path = "patches/option-ext" }
|
||||||
directories-next = { path = "patches/directories-next" }
|
directories-next = { path = "patches/directories-next" }
|
||||||
|
|
||||||
# The official pasta_curves repo doesn't support Zeroize
|
|
||||||
pasta_curves = { git = "https://github.com/kayabaNerve/pasta_curves", rev = "a46b5be95cacbff54d06aad8d3bbcba42e05d616" }
|
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
[workspace.lints.clippy]
|
||||||
unwrap_or_default = "allow"
|
unwrap_or_default = "allow"
|
||||||
map_unwrap_or = "allow"
|
map_unwrap_or = "allow"
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -5,4 +5,4 @@ a full copy of the AGPL-3.0 License is included in the root of this repository
|
|||||||
as a reference text. This copy should be provided with any distribution of a
|
as a reference text. This copy should be provided with any distribution of a
|
||||||
crate licensed under the AGPL-3.0, as per its terms.
|
crate licensed under the AGPL-3.0, as per its terms.
|
||||||
|
|
||||||
The GitHub actions (`.github/actions`) are licensed under the MIT license.
|
The GitHub actions/workflows (`.github`) are licensed under the MIT license.
|
||||||
|
|||||||
@@ -244,7 +244,16 @@ impl FieldElement {
|
|||||||
res *= res;
|
res *= res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res *= table[usize::from(bits)];
|
|
||||||
|
let mut scale_by = FieldElement::ONE;
|
||||||
|
#[allow(clippy::needless_range_loop)]
|
||||||
|
for i in 0 .. 16 {
|
||||||
|
#[allow(clippy::cast_possible_truncation)] // Safe since 0 .. 16
|
||||||
|
{
|
||||||
|
scale_by = <_>::conditional_select(&scale_by, &table[i], bits.ct_eq(&(i as u8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res *= scale_by;
|
||||||
bits = 0;
|
bits = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,7 +208,16 @@ impl Scalar {
|
|||||||
res *= res;
|
res *= res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res *= table[usize::from(bits)];
|
|
||||||
|
let mut scale_by = Scalar::ONE;
|
||||||
|
#[allow(clippy::needless_range_loop)]
|
||||||
|
for i in 0 .. 16 {
|
||||||
|
#[allow(clippy::cast_possible_truncation)] // Safe since 0 .. 16
|
||||||
|
{
|
||||||
|
scale_by = <_>::conditional_select(&scale_by, &table[i], bits.ct_eq(&(i as u8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res *= scale_by;
|
||||||
bits = 0;
|
bits = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,7 +161,16 @@ macro_rules! field {
|
|||||||
res *= res;
|
res *= res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res *= table[usize::from(bits)];
|
|
||||||
|
let mut scale_by = $FieldName(Residue::ONE);
|
||||||
|
#[allow(clippy::needless_range_loop)]
|
||||||
|
for i in 0 .. 16 {
|
||||||
|
#[allow(clippy::cast_possible_truncation)] // Safe since 0 .. 16
|
||||||
|
{
|
||||||
|
scale_by = <_>::conditional_select(&scale_by, &table[i], bits.ct_eq(&(i as u8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res *= scale_by;
|
||||||
bits = 0;
|
bits = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,16 @@ impl Mul<Scalar> for Point {
|
|||||||
res = res.double();
|
res = res.double();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res += table[usize::from(bits)];
|
|
||||||
|
let mut add_by = Point::identity();
|
||||||
|
#[allow(clippy::needless_range_loop)]
|
||||||
|
for i in 0 .. 16 {
|
||||||
|
#[allow(clippy::cast_possible_truncation)] // Safe since 0 .. 16
|
||||||
|
{
|
||||||
|
add_by = <_>::conditional_select(&add_by, &table[i], bits.ct_eq(&(i as u8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res += add_by;
|
||||||
bits = 0;
|
bits = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,10 @@ license-files = [
|
|||||||
multiple-versions = "warn"
|
multiple-versions = "warn"
|
||||||
wildcards = "warn"
|
wildcards = "warn"
|
||||||
highlight = "all"
|
highlight = "all"
|
||||||
deny = [ { name = "serde_derive", version = ">=1.0.172, <1.0.185" } ]
|
deny = [
|
||||||
|
{ name = "serde_derive", version = ">=1.0.172, <1.0.185" },
|
||||||
|
{ name = "hashbrown", version = ">=0.15" },
|
||||||
|
]
|
||||||
|
|
||||||
[sources]
|
[sources]
|
||||||
unknown-registry = "deny"
|
unknown-registry = "deny"
|
||||||
@@ -132,5 +135,4 @@ allow-git = [
|
|||||||
"https://github.com/serai-dex/substrate-bip39",
|
"https://github.com/serai-dex/substrate-bip39",
|
||||||
"https://github.com/serai-dex/substrate",
|
"https://github.com/serai-dex/substrate",
|
||||||
"https://github.com/kayabaNerve/pasta_curves",
|
"https://github.com/kayabaNerve/pasta_curves",
|
||||||
"https://github.com/alloy-rs/core",
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,20 +5,20 @@ GEM
|
|||||||
public_suffix (>= 2.0.2, < 7.0)
|
public_suffix (>= 2.0.2, < 7.0)
|
||||||
bigdecimal (3.1.8)
|
bigdecimal (3.1.8)
|
||||||
colorator (1.1.0)
|
colorator (1.1.0)
|
||||||
concurrent-ruby (1.3.3)
|
concurrent-ruby (1.3.4)
|
||||||
em-websocket (0.5.3)
|
em-websocket (0.5.3)
|
||||||
eventmachine (>= 0.12.9)
|
eventmachine (>= 0.12.9)
|
||||||
http_parser.rb (~> 0)
|
http_parser.rb (~> 0)
|
||||||
eventmachine (1.2.7)
|
eventmachine (1.2.7)
|
||||||
ffi (1.17.0-x86_64-linux-gnu)
|
ffi (1.17.0-x86_64-linux-gnu)
|
||||||
forwardable-extended (2.6.0)
|
forwardable-extended (2.6.0)
|
||||||
google-protobuf (4.27.3-x86_64-linux)
|
google-protobuf (4.28.2-x86_64-linux)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
rake (>= 13)
|
rake (>= 13)
|
||||||
http_parser.rb (0.8.0)
|
http_parser.rb (0.8.0)
|
||||||
i18n (1.14.5)
|
i18n (1.14.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
jekyll (4.3.3)
|
jekyll (4.3.4)
|
||||||
addressable (~> 2.4)
|
addressable (~> 2.4)
|
||||||
colorator (~> 1.0)
|
colorator (~> 1.0)
|
||||||
em-websocket (~> 0.5)
|
em-websocket (~> 0.5)
|
||||||
@@ -63,17 +63,15 @@ GEM
|
|||||||
rb-fsevent (0.11.2)
|
rb-fsevent (0.11.2)
|
||||||
rb-inotify (0.11.1)
|
rb-inotify (0.11.1)
|
||||||
ffi (~> 1.0)
|
ffi (~> 1.0)
|
||||||
rexml (3.3.4)
|
rexml (3.3.7)
|
||||||
strscan
|
rouge (4.4.0)
|
||||||
rouge (4.3.0)
|
|
||||||
safe_yaml (1.0.5)
|
safe_yaml (1.0.5)
|
||||||
sass-embedded (1.77.8-x86_64-linux-gnu)
|
sass-embedded (1.79.3-x86_64-linux-gnu)
|
||||||
google-protobuf (~> 4.26)
|
google-protobuf (~> 4.27)
|
||||||
strscan (3.1.0)
|
|
||||||
terminal-table (3.0.2)
|
terminal-table (3.0.2)
|
||||||
unicode-display_width (>= 1.1.1, < 3)
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
unicode-display_width (2.5.0)
|
unicode-display_width (2.6.0)
|
||||||
webrick (1.8.1)
|
webrick (1.8.2)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
x86_64-linux
|
x86_64-linux
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ pub(crate) use std::{
|
|||||||
pub(crate) use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
pub(crate) use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
||||||
pub(crate) use schnorr_signatures::SchnorrSignature;
|
pub(crate) use schnorr_signatures::SchnorrSignature;
|
||||||
|
|
||||||
pub(crate) use serai_primitives::NetworkId;
|
pub(crate) use serai_primitives::ExternalNetworkId;
|
||||||
|
|
||||||
pub(crate) use tokio::{
|
pub(crate) use tokio::{
|
||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
@@ -197,10 +197,7 @@ async fn main() {
|
|||||||
KEYS.write().unwrap().insert(service, key);
|
KEYS.write().unwrap().insert(service, key);
|
||||||
let mut queues = QUEUES.write().unwrap();
|
let mut queues = QUEUES.write().unwrap();
|
||||||
if service == Service::Coordinator {
|
if service == Service::Coordinator {
|
||||||
for network in serai_primitives::NETWORKS {
|
for network in serai_primitives::EXTERNAL_NETWORKS {
|
||||||
if network == NetworkId::Serai {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
queues.insert(
|
queues.insert(
|
||||||
(service, Service::Processor(network)),
|
(service, Service::Processor(network)),
|
||||||
RwLock::new(Queue(db.clone(), service, Service::Processor(network))),
|
RwLock::new(Queue(db.clone(), service, Service::Processor(network))),
|
||||||
@@ -214,17 +211,13 @@ async fn main() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Make queues for each NetworkId, other than Serai
|
// Make queues for each ExternalNetworkId
|
||||||
for network in serai_primitives::NETWORKS {
|
for network in serai_primitives::EXTERNAL_NETWORKS {
|
||||||
if network == NetworkId::Serai {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Use a match so we error if the list of NetworkIds changes
|
// Use a match so we error if the list of NetworkIds changes
|
||||||
let Some(key) = read_key(match network {
|
let Some(key) = read_key(match network {
|
||||||
NetworkId::Serai => unreachable!(),
|
ExternalNetworkId::Bitcoin => "BITCOIN_KEY",
|
||||||
NetworkId::Bitcoin => "BITCOIN_KEY",
|
ExternalNetworkId::Ethereum => "ETHEREUM_KEY",
|
||||||
NetworkId::Ethereum => "ETHEREUM_KEY",
|
ExternalNetworkId::Monero => "MONERO_KEY",
|
||||||
NetworkId::Monero => "MONERO_KEY",
|
|
||||||
}) else {
|
}) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};
|
|||||||
|
|
||||||
use borsh::{BorshSerialize, BorshDeserialize};
|
use borsh::{BorshSerialize, BorshDeserialize};
|
||||||
|
|
||||||
use serai_primitives::NetworkId;
|
use serai_primitives::ExternalNetworkId;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum Service {
|
pub enum Service {
|
||||||
Processor(NetworkId),
|
Processor(ExternalNetworkId),
|
||||||
Coordinator,
|
Coordinator,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "polyseed"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Rust implementation of Polyseed"
|
|
||||||
license = "MIT"
|
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/networks/monero/wallet/polyseed"
|
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
|
||||||
edition = "2021"
|
|
||||||
rust-version = "1.80"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
all-features = true
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
std-shims = { path = "../../../../common/std-shims", version = "^0.1.1", default-features = false }
|
|
||||||
|
|
||||||
thiserror = { version = "2", default-features = false }
|
|
||||||
|
|
||||||
subtle = { version = "^2.4", default-features = false }
|
|
||||||
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
|
||||||
rand_core = { version = "0.6", default-features = false }
|
|
||||||
|
|
||||||
sha3 = { version = "0.10", default-features = false }
|
|
||||||
pbkdf2 = { version = "0.12", features = ["simple"], default-features = false }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
hex = { version = "0.4", default-features = false, features = ["std"] }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
std = [
|
|
||||||
"std-shims/std",
|
|
||||||
|
|
||||||
"thiserror/std",
|
|
||||||
|
|
||||||
"subtle/std",
|
|
||||||
"zeroize/std",
|
|
||||||
"rand_core/std",
|
|
||||||
|
|
||||||
"sha3/std",
|
|
||||||
"pbkdf2/std",
|
|
||||||
]
|
|
||||||
default = ["std"]
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2022-2024 Luke Parker
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# Polyseed
|
|
||||||
|
|
||||||
Rust implementation of [Polyseed](https://github.com/tevador/polyseed).
|
|
||||||
|
|
||||||
This library is usable under no-std when the `std` feature (on by default) is
|
|
||||||
disabled.
|
|
||||||
|
|
||||||
### Cargo Features
|
|
||||||
|
|
||||||
- `std` (on by default): Enables `std` (and with it, more efficient internal
|
|
||||||
implementations).
|
|
||||||
@@ -1,472 +0,0 @@
|
|||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
||||||
#![doc = include_str!("../README.md")]
|
|
||||||
#![deny(missing_docs)]
|
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
|
||||||
|
|
||||||
use core::fmt;
|
|
||||||
use std_shims::{sync::LazyLock, string::String, collections::HashMap};
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
|
|
||||||
use subtle::ConstantTimeEq;
|
|
||||||
use zeroize::{Zeroize, Zeroizing, ZeroizeOnDrop};
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
|
||||||
|
|
||||||
use sha3::Sha3_256;
|
|
||||||
use pbkdf2::pbkdf2_hmac;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
// Features
|
|
||||||
const FEATURE_BITS: u8 = 5;
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const INTERNAL_FEATURES: u8 = 2;
|
|
||||||
const USER_FEATURES: u8 = 3;
|
|
||||||
|
|
||||||
const USER_FEATURES_MASK: u8 = (1 << USER_FEATURES) - 1;
|
|
||||||
const ENCRYPTED_MASK: u8 = 1 << 4;
|
|
||||||
const RESERVED_FEATURES_MASK: u8 = ((1 << FEATURE_BITS) - 1) ^ ENCRYPTED_MASK;
|
|
||||||
|
|
||||||
fn user_features(features: u8) -> u8 {
|
|
||||||
features & USER_FEATURES_MASK
|
|
||||||
}
|
|
||||||
|
|
||||||
fn polyseed_features_supported(features: u8) -> bool {
|
|
||||||
(features & RESERVED_FEATURES_MASK) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dates
|
|
||||||
const DATE_BITS: u8 = 10;
|
|
||||||
const DATE_MASK: u16 = (1u16 << DATE_BITS) - 1;
|
|
||||||
const POLYSEED_EPOCH: u64 = 1635768000; // 1st November 2021 12:00 UTC
|
|
||||||
const TIME_STEP: u64 = 2629746; // 30.436875 days = 1/12 of the Gregorian year
|
|
||||||
|
|
||||||
// After ~85 years, this will roll over.
|
|
||||||
fn birthday_encode(time: u64) -> u16 {
|
|
||||||
u16::try_from((time.saturating_sub(POLYSEED_EPOCH) / TIME_STEP) & u64::from(DATE_MASK))
|
|
||||||
.expect("value masked by 2**10 - 1 didn't fit into a u16")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn birthday_decode(birthday: u16) -> u64 {
|
|
||||||
POLYSEED_EPOCH + (u64::from(birthday) * TIME_STEP)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Polyseed parameters
|
|
||||||
const SECRET_BITS: usize = 150;
|
|
||||||
|
|
||||||
const BITS_PER_BYTE: usize = 8;
|
|
||||||
const SECRET_SIZE: usize = SECRET_BITS.div_ceil(BITS_PER_BYTE); // 19
|
|
||||||
const CLEAR_BITS: usize = (SECRET_SIZE * BITS_PER_BYTE) - SECRET_BITS; // 2
|
|
||||||
|
|
||||||
// Polyseed calls this CLEAR_MASK and has a very complicated formula for this fundamental
|
|
||||||
// equivalency
|
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
|
||||||
const LAST_BYTE_SECRET_BITS_MASK: u8 = ((1 << (BITS_PER_BYTE - CLEAR_BITS)) - 1) as u8;
|
|
||||||
|
|
||||||
const SECRET_BITS_PER_WORD: usize = 10;
|
|
||||||
|
|
||||||
// The amount of words in a seed.
|
|
||||||
const POLYSEED_LENGTH: usize = 16;
|
|
||||||
// Amount of characters each word must have if trimmed
|
|
||||||
pub(crate) const PREFIX_LEN: usize = 4;
|
|
||||||
|
|
||||||
const POLY_NUM_CHECK_DIGITS: usize = 1;
|
|
||||||
const DATA_WORDS: usize = POLYSEED_LENGTH - POLY_NUM_CHECK_DIGITS;
|
|
||||||
|
|
||||||
// Polynomial
|
|
||||||
const GF_BITS: usize = 11;
|
|
||||||
const POLYSEED_MUL2_TABLE: [u16; 8] = [5, 7, 1, 3, 13, 15, 9, 11];
|
|
||||||
|
|
||||||
type Poly = [u16; POLYSEED_LENGTH];
|
|
||||||
|
|
||||||
fn elem_mul2(x: u16) -> u16 {
|
|
||||||
if x < 1024 {
|
|
||||||
return 2 * x;
|
|
||||||
}
|
|
||||||
POLYSEED_MUL2_TABLE[usize::from(x % 8)] + (16 * ((x - 1024) / 8))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poly_eval(poly: &Poly) -> u16 {
|
|
||||||
// Horner's method at x = 2
|
|
||||||
let mut result = poly[POLYSEED_LENGTH - 1];
|
|
||||||
for i in (0 .. (POLYSEED_LENGTH - 1)).rev() {
|
|
||||||
result = elem_mul2(result) ^ poly[i];
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key gen parameters
|
|
||||||
const POLYSEED_SALT: &[u8] = b"POLYSEED key";
|
|
||||||
const POLYSEED_KEYGEN_ITERATIONS: u32 = 10000;
|
|
||||||
|
|
||||||
// Polyseed technically supports multiple coins, and the value for Monero is 0
|
|
||||||
// See: https://github.com/tevador/polyseed/blob/dfb05d8edb682b0e8f743b1b70c9131712ff4157
|
|
||||||
// /include/polyseed.h#L57
|
|
||||||
const COIN: u16 = 0;
|
|
||||||
|
|
||||||
/// An error when working with a Polyseed.
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, thiserror::Error)]
|
|
||||||
pub enum PolyseedError {
|
|
||||||
/// The seed was invalid.
|
|
||||||
#[error("invalid seed")]
|
|
||||||
InvalidSeed,
|
|
||||||
/// The entropy was invalid.
|
|
||||||
#[error("invalid entropy")]
|
|
||||||
InvalidEntropy,
|
|
||||||
/// The checksum did not match the data.
|
|
||||||
#[error("invalid checksum")]
|
|
||||||
InvalidChecksum,
|
|
||||||
/// Unsupported feature bits were set.
|
|
||||||
#[error("unsupported features")]
|
|
||||||
UnsupportedFeatures,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Language options for Polyseed.
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Zeroize)]
|
|
||||||
pub enum Language {
|
|
||||||
/// English language option.
|
|
||||||
English,
|
|
||||||
/// Spanish language option.
|
|
||||||
Spanish,
|
|
||||||
/// French language option.
|
|
||||||
French,
|
|
||||||
/// Italian language option.
|
|
||||||
Italian,
|
|
||||||
/// Japanese language option.
|
|
||||||
Japanese,
|
|
||||||
/// Korean language option.
|
|
||||||
Korean,
|
|
||||||
/// Czech language option.
|
|
||||||
Czech,
|
|
||||||
/// Portuguese language option.
|
|
||||||
Portuguese,
|
|
||||||
/// Simplified Chinese language option.
|
|
||||||
ChineseSimplified,
|
|
||||||
/// Traditional Chinese language option.
|
|
||||||
ChineseTraditional,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WordList {
|
|
||||||
words: &'static [&'static str],
|
|
||||||
has_prefix: bool,
|
|
||||||
has_accent: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WordList {
|
|
||||||
fn new(words: &'static [&'static str], has_prefix: bool, has_accent: bool) -> WordList {
|
|
||||||
let res = WordList { words, has_prefix, has_accent };
|
|
||||||
// This is needed for a later unwrap to not fails
|
|
||||||
assert!(words.len() < usize::from(u16::MAX));
|
|
||||||
res
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static LANGUAGES: LazyLock<HashMap<Language, WordList>> = LazyLock::new(|| {
|
|
||||||
HashMap::from([
|
|
||||||
(Language::Czech, WordList::new(include!("./words/cs.rs"), true, false)),
|
|
||||||
(Language::French, WordList::new(include!("./words/fr.rs"), true, true)),
|
|
||||||
(Language::Korean, WordList::new(include!("./words/ko.rs"), false, false)),
|
|
||||||
(Language::English, WordList::new(include!("./words/en.rs"), true, false)),
|
|
||||||
(Language::Italian, WordList::new(include!("./words/it.rs"), true, false)),
|
|
||||||
(Language::Spanish, WordList::new(include!("./words/es.rs"), true, true)),
|
|
||||||
(Language::Japanese, WordList::new(include!("./words/ja.rs"), false, false)),
|
|
||||||
(Language::Portuguese, WordList::new(include!("./words/pt.rs"), true, false)),
|
|
||||||
(
|
|
||||||
Language::ChineseSimplified,
|
|
||||||
WordList::new(include!("./words/zh_simplified.rs"), false, false),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
Language::ChineseTraditional,
|
|
||||||
WordList::new(include!("./words/zh_traditional.rs"), false, false),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
});
|
|
||||||
|
|
||||||
/// A Polyseed.
|
|
||||||
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
|
||||||
pub struct Polyseed {
|
|
||||||
language: Language,
|
|
||||||
features: u8,
|
|
||||||
birthday: u16,
|
|
||||||
entropy: Zeroizing<[u8; 32]>,
|
|
||||||
checksum: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Polyseed {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.debug_struct("Polyseed").finish_non_exhaustive()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn valid_entropy(entropy: &Zeroizing<[u8; 32]>) -> bool {
|
|
||||||
// Last byte of the entropy should only use certain bits
|
|
||||||
let mut res =
|
|
||||||
entropy[SECRET_SIZE - 1].ct_eq(&(entropy[SECRET_SIZE - 1] & LAST_BYTE_SECRET_BITS_MASK));
|
|
||||||
// Last 13 bytes of the buffer should be unused
|
|
||||||
for b in SECRET_SIZE .. entropy.len() {
|
|
||||||
res &= entropy[b].ct_eq(&0);
|
|
||||||
}
|
|
||||||
res.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Polyseed {
|
|
||||||
// TODO: Clean this
|
|
||||||
fn to_poly(&self) -> Poly {
|
|
||||||
let mut extra_bits = u32::from(FEATURE_BITS + DATE_BITS);
|
|
||||||
let extra_val = (u16::from(self.features) << DATE_BITS) | self.birthday;
|
|
||||||
|
|
||||||
let mut entropy_idx = 0;
|
|
||||||
let mut secret_bits = BITS_PER_BYTE;
|
|
||||||
let mut seed_rem_bits = SECRET_BITS - BITS_PER_BYTE;
|
|
||||||
|
|
||||||
let mut poly = [0; POLYSEED_LENGTH];
|
|
||||||
for i in 0 .. DATA_WORDS {
|
|
||||||
extra_bits -= 1;
|
|
||||||
|
|
||||||
let mut word_bits = 0;
|
|
||||||
let mut word_val = 0;
|
|
||||||
while word_bits < SECRET_BITS_PER_WORD {
|
|
||||||
if secret_bits == 0 {
|
|
||||||
entropy_idx += 1;
|
|
||||||
secret_bits = seed_rem_bits.min(BITS_PER_BYTE);
|
|
||||||
seed_rem_bits -= secret_bits;
|
|
||||||
}
|
|
||||||
let chunk_bits = secret_bits.min(SECRET_BITS_PER_WORD - word_bits);
|
|
||||||
secret_bits -= chunk_bits;
|
|
||||||
word_bits += chunk_bits;
|
|
||||||
word_val <<= chunk_bits;
|
|
||||||
word_val |=
|
|
||||||
(u16::from(self.entropy[entropy_idx]) >> secret_bits) & ((1u16 << chunk_bits) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
word_val <<= 1;
|
|
||||||
word_val |= (extra_val >> extra_bits) & 1;
|
|
||||||
poly[POLY_NUM_CHECK_DIGITS + i] = word_val;
|
|
||||||
}
|
|
||||||
|
|
||||||
poly
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_internal(
|
|
||||||
language: Language,
|
|
||||||
masked_features: u8,
|
|
||||||
encoded_birthday: u16,
|
|
||||||
entropy: Zeroizing<[u8; 32]>,
|
|
||||||
) -> Result<Polyseed, PolyseedError> {
|
|
||||||
if !polyseed_features_supported(masked_features) {
|
|
||||||
Err(PolyseedError::UnsupportedFeatures)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !valid_entropy(&entropy) {
|
|
||||||
Err(PolyseedError::InvalidEntropy)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut res = Polyseed {
|
|
||||||
language,
|
|
||||||
birthday: encoded_birthday,
|
|
||||||
features: masked_features,
|
|
||||||
entropy,
|
|
||||||
checksum: 0,
|
|
||||||
};
|
|
||||||
res.checksum = poly_eval(&res.to_poly());
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new `Polyseed` with specific internals.
|
|
||||||
///
|
|
||||||
/// `birthday` is defined in seconds since the epoch.
|
|
||||||
pub fn from(
|
|
||||||
language: Language,
|
|
||||||
features: u8,
|
|
||||||
birthday: u64,
|
|
||||||
entropy: Zeroizing<[u8; 32]>,
|
|
||||||
) -> Result<Polyseed, PolyseedError> {
|
|
||||||
Self::from_internal(language, user_features(features), birthday_encode(birthday), entropy)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new `Polyseed`.
|
|
||||||
///
|
|
||||||
/// This uses the system's time for the birthday, if available, else 0.
|
|
||||||
pub fn new<R: RngCore + CryptoRng>(rng: &mut R, language: Language) -> Polyseed {
|
|
||||||
// Get the birthday
|
|
||||||
#[cfg(feature = "std")]
|
|
||||||
let birthday =
|
|
||||||
SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or(core::time::Duration::ZERO).as_secs();
|
|
||||||
#[cfg(not(feature = "std"))]
|
|
||||||
let birthday = 0;
|
|
||||||
|
|
||||||
// Derive entropy
|
|
||||||
let mut entropy = Zeroizing::new([0; 32]);
|
|
||||||
rng.fill_bytes(entropy.as_mut());
|
|
||||||
entropy[SECRET_SIZE ..].fill(0);
|
|
||||||
entropy[SECRET_SIZE - 1] &= LAST_BYTE_SECRET_BITS_MASK;
|
|
||||||
|
|
||||||
Self::from(language, 0, birthday, entropy).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new `Polyseed` from a String.
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
|
||||||
pub fn from_string(lang: Language, seed: Zeroizing<String>) -> Result<Polyseed, PolyseedError> {
|
|
||||||
// Decode the seed into its polynomial coefficients
|
|
||||||
let mut poly = [0; POLYSEED_LENGTH];
|
|
||||||
|
|
||||||
// Validate words are in the lang word list
|
|
||||||
let lang_word_list: &WordList = &LANGUAGES[&lang];
|
|
||||||
for (i, word) in seed.split_whitespace().enumerate() {
|
|
||||||
// Find the word's index
|
|
||||||
fn check_if_matches<S: AsRef<str>, I: Iterator<Item = S>>(
|
|
||||||
has_prefix: bool,
|
|
||||||
mut lang_words: I,
|
|
||||||
word: &str,
|
|
||||||
) -> Option<usize> {
|
|
||||||
if has_prefix {
|
|
||||||
// Get the position of the word within the iterator
|
|
||||||
// Doesn't use starts_with and some words are substrs of others, leading to false
|
|
||||||
// positives
|
|
||||||
let mut get_position = || {
|
|
||||||
lang_words.position(|lang_word| {
|
|
||||||
let mut lang_word = lang_word.as_ref().chars();
|
|
||||||
let mut word = word.chars();
|
|
||||||
|
|
||||||
let mut res = true;
|
|
||||||
for _ in 0 .. PREFIX_LEN {
|
|
||||||
res &= lang_word.next() == word.next();
|
|
||||||
}
|
|
||||||
res
|
|
||||||
})
|
|
||||||
};
|
|
||||||
let res = get_position();
|
|
||||||
// If another word has this prefix, don't call it a match
|
|
||||||
if get_position().is_some() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
res
|
|
||||||
} else {
|
|
||||||
lang_words.position(|lang_word| lang_word.as_ref() == word)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(coeff) = (if lang_word_list.has_accent {
|
|
||||||
let ascii = |word: &str| word.chars().filter(char::is_ascii).collect::<String>();
|
|
||||||
check_if_matches(
|
|
||||||
lang_word_list.has_prefix,
|
|
||||||
lang_word_list.words.iter().map(|lang_word| ascii(lang_word)),
|
|
||||||
&ascii(word),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
check_if_matches(lang_word_list.has_prefix, lang_word_list.words.iter(), word)
|
|
||||||
}) else {
|
|
||||||
Err(PolyseedError::InvalidSeed)?
|
|
||||||
};
|
|
||||||
|
|
||||||
// WordList asserts the word list length is less than u16::MAX
|
|
||||||
poly[i] = u16::try_from(coeff).expect("coeff exceeded u16");
|
|
||||||
}
|
|
||||||
|
|
||||||
// xor out the coin
|
|
||||||
poly[POLY_NUM_CHECK_DIGITS] ^= COIN;
|
|
||||||
|
|
||||||
// Validate the checksum
|
|
||||||
if poly_eval(&poly) != 0 {
|
|
||||||
Err(PolyseedError::InvalidChecksum)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the polynomial into entropy
|
|
||||||
let mut entropy = Zeroizing::new([0; 32]);
|
|
||||||
|
|
||||||
let mut extra = 0;
|
|
||||||
|
|
||||||
let mut entropy_idx = 0;
|
|
||||||
let mut entropy_bits = 0;
|
|
||||||
|
|
||||||
let checksum = poly[0];
|
|
||||||
for mut word_val in poly.into_iter().skip(POLY_NUM_CHECK_DIGITS) {
|
|
||||||
// Parse the bottom bit, which is one of the bits of extra
|
|
||||||
// This iterates for less than 16 iters, meaning this won't drop any bits
|
|
||||||
extra <<= 1;
|
|
||||||
extra |= word_val & 1;
|
|
||||||
word_val >>= 1;
|
|
||||||
|
|
||||||
// 10 bits per word creates a [8, 2], [6, 4], [4, 6], [2, 8] cycle
|
|
||||||
// 15 % 4 is 3, leaving 2 bits off, and 152 (19 * 8) - 2 is 150, the amount of bits in the
|
|
||||||
// secret
|
|
||||||
let mut word_bits = GF_BITS - 1;
|
|
||||||
while word_bits > 0 {
|
|
||||||
if entropy_bits == BITS_PER_BYTE {
|
|
||||||
entropy_idx += 1;
|
|
||||||
entropy_bits = 0;
|
|
||||||
}
|
|
||||||
let chunk_bits = word_bits.min(BITS_PER_BYTE - entropy_bits);
|
|
||||||
word_bits -= chunk_bits;
|
|
||||||
let chunk_mask = (1u16 << chunk_bits) - 1;
|
|
||||||
if chunk_bits < BITS_PER_BYTE {
|
|
||||||
entropy[entropy_idx] <<= chunk_bits;
|
|
||||||
}
|
|
||||||
entropy[entropy_idx] |=
|
|
||||||
u8::try_from((word_val >> word_bits) & chunk_mask).expect("chunk exceeded u8");
|
|
||||||
entropy_bits += chunk_bits;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let birthday = extra & DATE_MASK;
|
|
||||||
// extra is contained to u16, and DATE_BITS > 8
|
|
||||||
let features =
|
|
||||||
u8::try_from(extra >> DATE_BITS).expect("couldn't convert extra >> DATE_BITS to u8");
|
|
||||||
|
|
||||||
let res = Self::from_internal(lang, features, birthday, entropy);
|
|
||||||
if let Ok(res) = res.as_ref() {
|
|
||||||
debug_assert_eq!(res.checksum, checksum);
|
|
||||||
}
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
/// When this seed was created, defined in seconds since the epoch.
|
|
||||||
pub fn birthday(&self) -> u64 {
|
|
||||||
birthday_decode(self.birthday)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This seed's features.
|
|
||||||
pub fn features(&self) -> u8 {
|
|
||||||
self.features
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This seed's entropy.
|
|
||||||
pub fn entropy(&self) -> &Zeroizing<[u8; 32]> {
|
|
||||||
&self.entropy
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The key derived from this seed.
|
|
||||||
pub fn key(&self) -> Zeroizing<[u8; 32]> {
|
|
||||||
let mut key = Zeroizing::new([0; 32]);
|
|
||||||
pbkdf2_hmac::<Sha3_256>(
|
|
||||||
self.entropy.as_slice(),
|
|
||||||
POLYSEED_SALT,
|
|
||||||
POLYSEED_KEYGEN_ITERATIONS,
|
|
||||||
key.as_mut(),
|
|
||||||
);
|
|
||||||
key
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The String representation of this seed.
|
|
||||||
pub fn to_string(&self) -> Zeroizing<String> {
|
|
||||||
// Encode the polynomial with the existing checksum
|
|
||||||
let mut poly = self.to_poly();
|
|
||||||
poly[0] = self.checksum;
|
|
||||||
|
|
||||||
// Embed the coin
|
|
||||||
poly[POLY_NUM_CHECK_DIGITS] ^= COIN;
|
|
||||||
|
|
||||||
// Output words
|
|
||||||
let mut seed = Zeroizing::new(String::new());
|
|
||||||
let words = &LANGUAGES[&self.language].words;
|
|
||||||
for i in 0 .. poly.len() {
|
|
||||||
seed.push_str(words[usize::from(poly[i])]);
|
|
||||||
if i < poly.len() - 1 {
|
|
||||||
seed.push(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
seed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
use zeroize::Zeroizing;
|
|
||||||
use rand_core::OsRng;
|
|
||||||
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_polyseed() {
|
|
||||||
struct Vector {
|
|
||||||
language: Language,
|
|
||||||
seed: String,
|
|
||||||
entropy: String,
|
|
||||||
birthday: u64,
|
|
||||||
has_prefix: bool,
|
|
||||||
has_accent: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
let vectors = [
|
|
||||||
Vector {
|
|
||||||
language: Language::English,
|
|
||||||
seed: "raven tail swear infant grief assist regular lamp \
|
|
||||||
duck valid someone little harsh puppy airport language"
|
|
||||||
.into(),
|
|
||||||
entropy: "dd76e7359a0ded37cd0ff0f3c829a5ae01673300000000000000000000000000".into(),
|
|
||||||
birthday: 1638446400,
|
|
||||||
has_prefix: true,
|
|
||||||
has_accent: false,
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Spanish,
|
|
||||||
seed: "eje fin parte célebre tabú pestaña lienzo puma \
|
|
||||||
prisión hora regalo lengua existir lápiz lote sonoro"
|
|
||||||
.into(),
|
|
||||||
entropy: "5a2b02df7db21fcbe6ec6df137d54c7b20fd2b00000000000000000000000000".into(),
|
|
||||||
birthday: 3118651200,
|
|
||||||
has_prefix: true,
|
|
||||||
has_accent: true,
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::French,
|
|
||||||
seed: "valable arracher décaler jeudi amusant dresser mener épaissir risible \
|
|
||||||
prouesse réserve ampleur ajuster muter caméra enchère"
|
|
||||||
.into(),
|
|
||||||
entropy: "11cfd870324b26657342c37360c424a14a050b00000000000000000000000000".into(),
|
|
||||||
birthday: 1679314966,
|
|
||||||
has_prefix: true,
|
|
||||||
has_accent: true,
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Italian,
|
|
||||||
seed: "caduco midollo copione meninge isotopo illogico riflesso tartaruga fermento \
|
|
||||||
olandese normale tristezza episodio voragine forbito achille"
|
|
||||||
.into(),
|
|
||||||
entropy: "7ecc57c9b4652d4e31428f62bec91cfd55500600000000000000000000000000".into(),
|
|
||||||
birthday: 1679316358,
|
|
||||||
has_prefix: true,
|
|
||||||
has_accent: false,
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Portuguese,
|
|
||||||
seed: "caverna custear azedo adeus senador apertada sedoso omitir \
|
|
||||||
sujeito aurora videira molho cartaz gesso dentista tapar"
|
|
||||||
.into(),
|
|
||||||
entropy: "45473063711376cae38f1b3eba18c874124e1d00000000000000000000000000".into(),
|
|
||||||
birthday: 1679316657,
|
|
||||||
has_prefix: true,
|
|
||||||
has_accent: false,
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Czech,
|
|
||||||
seed: "usmrtit nora dotaz komunita zavalit funkce mzda sotva akce \
|
|
||||||
vesta kabel herna stodola uvolnit ustrnout email"
|
|
||||||
.into(),
|
|
||||||
entropy: "7ac8a4efd62d9c3c4c02e350d32326df37821c00000000000000000000000000".into(),
|
|
||||||
birthday: 1679316898,
|
|
||||||
has_prefix: true,
|
|
||||||
has_accent: false,
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Korean,
|
|
||||||
seed: "전망 선풍기 국제 무궁화 설사 기름 이론적 해안 절망 예선 \
|
|
||||||
지우개 보관 절망 말기 시각 귀신"
|
|
||||||
.into(),
|
|
||||||
entropy: "684663fda420298f42ed94b2c512ed38ddf12b00000000000000000000000000".into(),
|
|
||||||
birthday: 1679317073,
|
|
||||||
has_prefix: false,
|
|
||||||
has_accent: false,
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Japanese,
|
|
||||||
seed: "うちあわせ ちつじょ つごう しはい けんこう とおる てみやげ はんとし たんとう \
|
|
||||||
といれ おさない おさえる むかう ぬぐう なふだ せまる"
|
|
||||||
.into(),
|
|
||||||
entropy: "94e6665518a6286c6e3ba508a2279eb62b771f00000000000000000000000000".into(),
|
|
||||||
birthday: 1679318722,
|
|
||||||
has_prefix: false,
|
|
||||||
has_accent: false,
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::ChineseTraditional,
|
|
||||||
seed: "亂 挖 斤 柄 代 圈 枝 轄 魯 論 函 開 勘 番 榮 壁".into(),
|
|
||||||
entropy: "b1594f585987ab0fd5a31da1f0d377dae5283f00000000000000000000000000".into(),
|
|
||||||
birthday: 1679426433,
|
|
||||||
has_prefix: false,
|
|
||||||
has_accent: false,
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::ChineseSimplified,
|
|
||||||
seed: "啊 百 族 府 票 划 伪 仓 叶 虾 借 溜 晨 左 等 鬼".into(),
|
|
||||||
entropy: "21cdd366f337b89b8d1bc1df9fe73047c22b0300000000000000000000000000".into(),
|
|
||||||
birthday: 1679426817,
|
|
||||||
has_prefix: false,
|
|
||||||
has_accent: false,
|
|
||||||
},
|
|
||||||
// The following seed requires the language specification in order to calculate
|
|
||||||
// a single valid checksum
|
|
||||||
Vector {
|
|
||||||
language: Language::Spanish,
|
|
||||||
seed: "impo sort usua cabi venu nobl oliv clim \
|
|
||||||
cont barr marc auto prod vaca torn fati"
|
|
||||||
.into(),
|
|
||||||
entropy: "dbfce25fe09b68a340e01c62417eeef43ad51800000000000000000000000000".into(),
|
|
||||||
birthday: 1701511650,
|
|
||||||
has_prefix: true,
|
|
||||||
has_accent: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for vector in vectors {
|
|
||||||
let add_whitespace = |mut seed: String| {
|
|
||||||
seed.push(' ');
|
|
||||||
seed
|
|
||||||
};
|
|
||||||
|
|
||||||
let seed_without_accents = |seed: &str| {
|
|
||||||
seed
|
|
||||||
.split_whitespace()
|
|
||||||
.map(|w| w.chars().filter(char::is_ascii).collect::<String>())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(" ")
|
|
||||||
};
|
|
||||||
|
|
||||||
let trim_seed = |seed: &str| {
|
|
||||||
let seed_to_trim =
|
|
||||||
if vector.has_accent { seed_without_accents(seed) } else { seed.to_string() };
|
|
||||||
seed_to_trim
|
|
||||||
.split_whitespace()
|
|
||||||
.map(|w| {
|
|
||||||
let mut ascii = 0;
|
|
||||||
let mut to_take = w.len();
|
|
||||||
for (i, char) in w.chars().enumerate() {
|
|
||||||
if char.is_ascii() {
|
|
||||||
ascii += 1;
|
|
||||||
}
|
|
||||||
if ascii == PREFIX_LEN {
|
|
||||||
// +1 to include this character, which put us at the prefix length
|
|
||||||
to_take = i + 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.chars().take(to_take).collect::<String>()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(" ")
|
|
||||||
};
|
|
||||||
|
|
||||||
// String -> Seed
|
|
||||||
println!("{}. language: {:?}, seed: {}", line!(), vector.language, vector.seed.clone());
|
|
||||||
let seed = Polyseed::from_string(vector.language, Zeroizing::new(vector.seed.clone())).unwrap();
|
|
||||||
let trim = trim_seed(&vector.seed);
|
|
||||||
let add_whitespace = add_whitespace(vector.seed.clone());
|
|
||||||
let seed_without_accents = seed_without_accents(&vector.seed);
|
|
||||||
|
|
||||||
// Make sure a version with added whitespace still works
|
|
||||||
let whitespaced_seed =
|
|
||||||
Polyseed::from_string(vector.language, Zeroizing::new(add_whitespace)).unwrap();
|
|
||||||
assert_eq!(seed, whitespaced_seed);
|
|
||||||
// Check trimmed versions works
|
|
||||||
if vector.has_prefix {
|
|
||||||
let trimmed_seed = Polyseed::from_string(vector.language, Zeroizing::new(trim)).unwrap();
|
|
||||||
assert_eq!(seed, trimmed_seed);
|
|
||||||
}
|
|
||||||
// Check versions without accents work
|
|
||||||
if vector.has_accent {
|
|
||||||
let seed_without_accents =
|
|
||||||
Polyseed::from_string(vector.language, Zeroizing::new(seed_without_accents)).unwrap();
|
|
||||||
assert_eq!(seed, seed_without_accents);
|
|
||||||
}
|
|
||||||
|
|
||||||
let entropy = Zeroizing::new(hex::decode(vector.entropy).unwrap().try_into().unwrap());
|
|
||||||
assert_eq!(*seed.entropy(), entropy);
|
|
||||||
assert!(seed.birthday().abs_diff(vector.birthday) < TIME_STEP);
|
|
||||||
|
|
||||||
// Entropy -> Seed
|
|
||||||
let from_entropy = Polyseed::from(vector.language, 0, seed.birthday(), entropy).unwrap();
|
|
||||||
assert_eq!(seed.to_string(), from_entropy.to_string());
|
|
||||||
|
|
||||||
// Check against ourselves
|
|
||||||
{
|
|
||||||
let seed = Polyseed::new(&mut OsRng, vector.language);
|
|
||||||
println!("{}. seed: {}", line!(), *seed.to_string());
|
|
||||||
assert_eq!(seed, Polyseed::from_string(vector.language, seed.to_string()).unwrap());
|
|
||||||
assert_eq!(
|
|
||||||
seed,
|
|
||||||
Polyseed::from(vector.language, 0, seed.birthday(), seed.entropy().clone(),).unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_invalid_polyseed() {
|
|
||||||
// This seed includes unsupported features bits and should error on decode
|
|
||||||
let seed = "include domain claim resemble urban hire lunch bird \
|
|
||||||
crucial fire best wife ring warm ignore model"
|
|
||||||
.into();
|
|
||||||
let res = Polyseed::from_string(Language::English, Zeroizing::new(seed));
|
|
||||||
assert_eq!(res, Err(PolyseedError::UnsupportedFeatures));
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,41 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "monero-seed"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Rust implementation of Monero's seed algorithm"
|
|
||||||
license = "MIT"
|
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/networks/monero/wallet/seed"
|
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
|
||||||
edition = "2021"
|
|
||||||
rust-version = "1.80"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
all-features = true
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
std-shims = { path = "../../../../common/std-shims", version = "^0.1.1", default-features = false }
|
|
||||||
|
|
||||||
thiserror = { version = "2", default-features = false }
|
|
||||||
|
|
||||||
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
|
||||||
rand_core = { version = "0.6", default-features = false }
|
|
||||||
|
|
||||||
curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
hex = { version = "0.4", default-features = false, features = ["std"] }
|
|
||||||
monero-primitives = { path = "../../primitives", default-features = false, features = ["std"] }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
std = [
|
|
||||||
"std-shims/std",
|
|
||||||
|
|
||||||
"thiserror/std",
|
|
||||||
|
|
||||||
"zeroize/std",
|
|
||||||
"rand_core/std",
|
|
||||||
]
|
|
||||||
default = ["std"]
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2022-2024 Luke Parker
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
# Monero Seeds
|
|
||||||
|
|
||||||
Rust implementation of Monero's seed algorithm.
|
|
||||||
|
|
||||||
This library is usable under no-std when the `std` feature (on by default) is
|
|
||||||
disabled.
|
|
||||||
|
|
||||||
### Cargo Features
|
|
||||||
|
|
||||||
- `std` (on by default): Enables `std` (and with it, more efficient internal
|
|
||||||
implementations).
|
|
||||||
@@ -1,352 +0,0 @@
|
|||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
||||||
#![doc = include_str!("../README.md")]
|
|
||||||
#![deny(missing_docs)]
|
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
|
||||||
|
|
||||||
use core::{ops::Deref, fmt};
|
|
||||||
use std_shims::{
|
|
||||||
sync::LazyLock,
|
|
||||||
vec,
|
|
||||||
vec::Vec,
|
|
||||||
string::{String, ToString},
|
|
||||||
collections::HashMap,
|
|
||||||
};
|
|
||||||
|
|
||||||
use zeroize::{Zeroize, Zeroizing};
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
|
||||||
|
|
||||||
use curve25519_dalek::scalar::Scalar;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
// The amount of words in a seed without a checksum.
|
|
||||||
const SEED_LENGTH: usize = 24;
|
|
||||||
// The amount of words in a seed with a checksum.
|
|
||||||
const SEED_LENGTH_WITH_CHECKSUM: usize = 25;
|
|
||||||
|
|
||||||
/// An error when working with a seed.
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, thiserror::Error)]
|
|
||||||
pub enum SeedError {
|
|
||||||
#[error("invalid seed")]
|
|
||||||
/// The seed was invalid.
|
|
||||||
InvalidSeed,
|
|
||||||
/// The checksum did not match the data.
|
|
||||||
#[error("invalid checksum")]
|
|
||||||
InvalidChecksum,
|
|
||||||
/// The deprecated English language option was used with a checksum.
|
|
||||||
///
|
|
||||||
/// The deprecated English language option did not include a checksum.
|
|
||||||
#[error("deprecated English language option included a checksum")]
|
|
||||||
DeprecatedEnglishWithChecksum,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Language options.
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Zeroize)]
|
|
||||||
pub enum Language {
|
|
||||||
/// Chinese language option.
|
|
||||||
Chinese,
|
|
||||||
/// English language option.
|
|
||||||
English,
|
|
||||||
/// Dutch language option.
|
|
||||||
Dutch,
|
|
||||||
/// French language option.
|
|
||||||
French,
|
|
||||||
/// Spanish language option.
|
|
||||||
Spanish,
|
|
||||||
/// German language option.
|
|
||||||
German,
|
|
||||||
/// Italian language option.
|
|
||||||
Italian,
|
|
||||||
/// Portuguese language option.
|
|
||||||
Portuguese,
|
|
||||||
/// Japanese language option.
|
|
||||||
Japanese,
|
|
||||||
/// Russian language option.
|
|
||||||
Russian,
|
|
||||||
/// Esperanto language option.
|
|
||||||
Esperanto,
|
|
||||||
/// Lojban language option.
|
|
||||||
Lojban,
|
|
||||||
/// The original, and deprecated, English language.
|
|
||||||
DeprecatedEnglish,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn trim(word: &str, len: usize) -> Zeroizing<String> {
|
|
||||||
Zeroizing::new(word.chars().take(len).collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WordList {
|
|
||||||
word_list: &'static [&'static str],
|
|
||||||
word_map: HashMap<&'static str, usize>,
|
|
||||||
trimmed_word_map: HashMap<String, usize>,
|
|
||||||
unique_prefix_length: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WordList {
|
|
||||||
fn new(word_list: &'static [&'static str], prefix_length: usize) -> WordList {
|
|
||||||
let mut lang = WordList {
|
|
||||||
word_list,
|
|
||||||
word_map: HashMap::new(),
|
|
||||||
trimmed_word_map: HashMap::new(),
|
|
||||||
unique_prefix_length: prefix_length,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (i, word) in lang.word_list.iter().enumerate() {
|
|
||||||
lang.word_map.insert(word, i);
|
|
||||||
lang.trimmed_word_map.insert(trim(word, lang.unique_prefix_length).deref().clone(), i);
|
|
||||||
}
|
|
||||||
|
|
||||||
lang
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static LANGUAGES: LazyLock<HashMap<Language, WordList>> = LazyLock::new(|| {
|
|
||||||
HashMap::from([
|
|
||||||
(Language::Chinese, WordList::new(include!("./words/zh.rs"), 1)),
|
|
||||||
(Language::English, WordList::new(include!("./words/en.rs"), 3)),
|
|
||||||
(Language::Dutch, WordList::new(include!("./words/nl.rs"), 4)),
|
|
||||||
(Language::French, WordList::new(include!("./words/fr.rs"), 4)),
|
|
||||||
(Language::Spanish, WordList::new(include!("./words/es.rs"), 4)),
|
|
||||||
(Language::German, WordList::new(include!("./words/de.rs"), 4)),
|
|
||||||
(Language::Italian, WordList::new(include!("./words/it.rs"), 4)),
|
|
||||||
(Language::Portuguese, WordList::new(include!("./words/pt.rs"), 4)),
|
|
||||||
(Language::Japanese, WordList::new(include!("./words/ja.rs"), 3)),
|
|
||||||
(Language::Russian, WordList::new(include!("./words/ru.rs"), 4)),
|
|
||||||
(Language::Esperanto, WordList::new(include!("./words/eo.rs"), 4)),
|
|
||||||
(Language::Lojban, WordList::new(include!("./words/jbo.rs"), 4)),
|
|
||||||
(Language::DeprecatedEnglish, WordList::new(include!("./words/ang.rs"), 4)),
|
|
||||||
])
|
|
||||||
});
|
|
||||||
|
|
||||||
fn checksum_index(words: &[Zeroizing<String>], lang: &WordList) -> usize {
|
|
||||||
let mut trimmed_words = Zeroizing::new(String::new());
|
|
||||||
for w in words {
|
|
||||||
*trimmed_words += &trim(w, lang.unique_prefix_length);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn crc32_table() -> [u32; 256] {
|
|
||||||
let poly = 0xedb88320u32;
|
|
||||||
|
|
||||||
let mut res = [0; 256];
|
|
||||||
let mut i = 0;
|
|
||||||
while i < 256 {
|
|
||||||
let mut entry = i;
|
|
||||||
let mut b = 0;
|
|
||||||
while b < 8 {
|
|
||||||
let trigger = entry & 1;
|
|
||||||
entry >>= 1;
|
|
||||||
if trigger == 1 {
|
|
||||||
entry ^= poly;
|
|
||||||
}
|
|
||||||
b += 1;
|
|
||||||
}
|
|
||||||
res[i as usize] = entry;
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
|
||||||
const CRC32_TABLE: [u32; 256] = crc32_table();
|
|
||||||
|
|
||||||
let trimmed_words = trimmed_words.as_bytes();
|
|
||||||
let mut checksum = u32::MAX;
|
|
||||||
for i in 0 .. trimmed_words.len() {
|
|
||||||
checksum = CRC32_TABLE[usize::from(u8::try_from(checksum % 256).unwrap() ^ trimmed_words[i])] ^
|
|
||||||
(checksum >> 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
usize::try_from(!checksum).unwrap() % words.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a private key to a seed
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
|
||||||
fn key_to_seed(lang: Language, key: Zeroizing<Scalar>) -> Seed {
|
|
||||||
let bytes = Zeroizing::new(key.to_bytes());
|
|
||||||
|
|
||||||
// get the language words
|
|
||||||
let words = &LANGUAGES[&lang].word_list;
|
|
||||||
let list_len = u64::try_from(words.len()).unwrap();
|
|
||||||
|
|
||||||
// To store the found words & add the checksum word later.
|
|
||||||
let mut seed = Vec::with_capacity(25);
|
|
||||||
|
|
||||||
// convert to words
|
|
||||||
// 4 bytes -> 3 words. 8 digits base 16 -> 3 digits base 1626
|
|
||||||
let mut segment = [0; 4];
|
|
||||||
let mut indices = [0; 4];
|
|
||||||
for i in 0 .. 8 {
|
|
||||||
// convert first 4 byte to u32 & get the word indices
|
|
||||||
let start = i * 4;
|
|
||||||
// convert 4 byte to u32
|
|
||||||
segment.copy_from_slice(&bytes[start .. (start + 4)]);
|
|
||||||
// Actually convert to a u64 so we can add without overflowing
|
|
||||||
indices[0] = u64::from(u32::from_le_bytes(segment));
|
|
||||||
indices[1] = indices[0];
|
|
||||||
indices[0] /= list_len;
|
|
||||||
indices[2] = indices[0] + indices[1];
|
|
||||||
indices[0] /= list_len;
|
|
||||||
indices[3] = indices[0] + indices[2];
|
|
||||||
|
|
||||||
// append words to seed
|
|
||||||
for i in indices.iter().skip(1) {
|
|
||||||
let word = usize::try_from(i % list_len).unwrap();
|
|
||||||
seed.push(Zeroizing::new(words[word].to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
segment.zeroize();
|
|
||||||
indices.zeroize();
|
|
||||||
|
|
||||||
// create a checksum word for all languages except old english
|
|
||||||
if lang != Language::DeprecatedEnglish {
|
|
||||||
let checksum = seed[checksum_index(&seed, &LANGUAGES[&lang])].clone();
|
|
||||||
seed.push(checksum);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut res = Zeroizing::new(String::new());
|
|
||||||
for (i, word) in seed.iter().enumerate() {
|
|
||||||
if i != 0 {
|
|
||||||
*res += " ";
|
|
||||||
}
|
|
||||||
*res += word;
|
|
||||||
}
|
|
||||||
Seed(lang, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a seed to bytes
|
|
||||||
fn seed_to_bytes(lang: Language, words: &str) -> Result<Zeroizing<[u8; 32]>, SeedError> {
|
|
||||||
// get seed words
|
|
||||||
let words = words.split_whitespace().map(|w| Zeroizing::new(w.to_string())).collect::<Vec<_>>();
|
|
||||||
if (words.len() != SEED_LENGTH) && (words.len() != SEED_LENGTH_WITH_CHECKSUM) {
|
|
||||||
panic!("invalid seed passed to seed_to_bytes");
|
|
||||||
}
|
|
||||||
|
|
||||||
let has_checksum = words.len() == SEED_LENGTH_WITH_CHECKSUM;
|
|
||||||
if has_checksum && lang == Language::DeprecatedEnglish {
|
|
||||||
Err(SeedError::DeprecatedEnglishWithChecksum)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate words are in the language word list
|
|
||||||
let lang_word_list: &WordList = &LANGUAGES[&lang];
|
|
||||||
let matched_indices = (|| {
|
|
||||||
let has_checksum = words.len() == SEED_LENGTH_WITH_CHECKSUM;
|
|
||||||
let mut matched_indices = Zeroizing::new(vec![]);
|
|
||||||
|
|
||||||
// Iterate through all the words and see if they're all present
|
|
||||||
for word in &words {
|
|
||||||
let trimmed = trim(word, lang_word_list.unique_prefix_length);
|
|
||||||
let word = if has_checksum { &trimmed } else { word };
|
|
||||||
|
|
||||||
if let Some(index) = if has_checksum {
|
|
||||||
lang_word_list.trimmed_word_map.get(word.deref())
|
|
||||||
} else {
|
|
||||||
lang_word_list.word_map.get(&word.as_str())
|
|
||||||
} {
|
|
||||||
matched_indices.push(*index);
|
|
||||||
} else {
|
|
||||||
Err(SeedError::InvalidSeed)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if has_checksum {
|
|
||||||
// exclude the last word when calculating a checksum.
|
|
||||||
let last_word = words.last().unwrap().clone();
|
|
||||||
let checksum = words[checksum_index(&words[.. words.len() - 1], lang_word_list)].clone();
|
|
||||||
|
|
||||||
// check the trimmed checksum and trimmed last word line up
|
|
||||||
if trim(&checksum, lang_word_list.unique_prefix_length) !=
|
|
||||||
trim(&last_word, lang_word_list.unique_prefix_length)
|
|
||||||
{
|
|
||||||
Err(SeedError::InvalidChecksum)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(matched_indices)
|
|
||||||
})()?;
|
|
||||||
|
|
||||||
// convert to bytes
|
|
||||||
let mut res = Zeroizing::new([0; 32]);
|
|
||||||
let mut indices = Zeroizing::new([0; 4]);
|
|
||||||
for i in 0 .. 8 {
|
|
||||||
// read 3 indices at a time
|
|
||||||
let i3 = i * 3;
|
|
||||||
indices[1] = matched_indices[i3];
|
|
||||||
indices[2] = matched_indices[i3 + 1];
|
|
||||||
indices[3] = matched_indices[i3 + 2];
|
|
||||||
|
|
||||||
let inner = |i| {
|
|
||||||
let mut base = (lang_word_list.word_list.len() - indices[i] + indices[i + 1]) %
|
|
||||||
lang_word_list.word_list.len();
|
|
||||||
// Shift the index over
|
|
||||||
for _ in 0 .. i {
|
|
||||||
base *= lang_word_list.word_list.len();
|
|
||||||
}
|
|
||||||
base
|
|
||||||
};
|
|
||||||
// set the last index
|
|
||||||
indices[0] = indices[1] + inner(1) + inner(2);
|
|
||||||
if (indices[0] % lang_word_list.word_list.len()) != indices[1] {
|
|
||||||
Err(SeedError::InvalidSeed)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pos = i * 4;
|
|
||||||
let mut bytes = u32::try_from(indices[0]).unwrap().to_le_bytes();
|
|
||||||
res[pos .. (pos + 4)].copy_from_slice(&bytes);
|
|
||||||
bytes.zeroize();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A Monero seed.
|
|
||||||
#[derive(Clone, PartialEq, Eq, Zeroize)]
|
|
||||||
pub struct Seed(Language, Zeroizing<String>);
|
|
||||||
|
|
||||||
impl fmt::Debug for Seed {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
f.debug_struct("Seed").finish_non_exhaustive()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Seed {
|
|
||||||
/// Create a new seed.
|
|
||||||
pub fn new<R: RngCore + CryptoRng>(rng: &mut R, lang: Language) -> Seed {
|
|
||||||
let mut scalar_bytes = Zeroizing::new([0; 64]);
|
|
||||||
rng.fill_bytes(scalar_bytes.as_mut());
|
|
||||||
key_to_seed(lang, Zeroizing::new(Scalar::from_bytes_mod_order_wide(scalar_bytes.deref())))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a seed from a string.
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
|
||||||
pub fn from_string(lang: Language, words: Zeroizing<String>) -> Result<Seed, SeedError> {
|
|
||||||
let entropy = seed_to_bytes(lang, &words)?;
|
|
||||||
|
|
||||||
// Make sure this is a valid scalar
|
|
||||||
let scalar = Scalar::from_canonical_bytes(*entropy);
|
|
||||||
if scalar.is_none().into() {
|
|
||||||
Err(SeedError::InvalidSeed)?;
|
|
||||||
}
|
|
||||||
let mut scalar = scalar.unwrap();
|
|
||||||
scalar.zeroize();
|
|
||||||
|
|
||||||
// Call from_entropy so a trimmed seed becomes a full seed
|
|
||||||
Ok(Self::from_entropy(lang, entropy).unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a seed from entropy.
|
|
||||||
#[allow(clippy::needless_pass_by_value)]
|
|
||||||
pub fn from_entropy(lang: Language, entropy: Zeroizing<[u8; 32]>) -> Option<Seed> {
|
|
||||||
Option::from(Scalar::from_canonical_bytes(*entropy))
|
|
||||||
.map(|scalar| key_to_seed(lang, Zeroizing::new(scalar)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert a seed to a string.
|
|
||||||
pub fn to_string(&self) -> Zeroizing<String> {
|
|
||||||
self.1.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the entropy underlying this seed.
|
|
||||||
pub fn entropy(&self) -> Zeroizing<[u8; 32]> {
|
|
||||||
seed_to_bytes(self.0, &self.1).unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,234 +0,0 @@
|
|||||||
use zeroize::Zeroizing;
|
|
||||||
use rand_core::OsRng;
|
|
||||||
|
|
||||||
use curve25519_dalek::scalar::Scalar;
|
|
||||||
|
|
||||||
use monero_primitives::keccak256;
|
|
||||||
|
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_original_seed() {
|
|
||||||
struct Vector {
|
|
||||||
language: Language,
|
|
||||||
seed: String,
|
|
||||||
spend: String,
|
|
||||||
view: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
let vectors = [
|
|
||||||
Vector {
|
|
||||||
language: Language::Chinese,
|
|
||||||
seed: "摇 曲 艺 武 滴 然 效 似 赏 式 祥 歌 买 疑 小 碧 堆 博 键 房 鲜 悲 付 喷 武".into(),
|
|
||||||
spend: "a5e4fff1706ef9212993a69f246f5c95ad6d84371692d63e9bb0ea112a58340d".into(),
|
|
||||||
view: "1176c43ce541477ea2f3ef0b49b25112b084e26b8a843e1304ac4677b74cdf02".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::English,
|
|
||||||
seed: "washing thirsty occur lectures tuesday fainted toxic adapt \
|
|
||||||
abnormal memoir nylon mostly building shrugged online ember northern \
|
|
||||||
ruby woes dauntless boil family illness inroads northern"
|
|
||||||
.into(),
|
|
||||||
spend: "c0af65c0dd837e666b9d0dfed62745f4df35aed7ea619b2798a709f0fe545403".into(),
|
|
||||||
view: "513ba91c538a5a9069e0094de90e927c0cd147fa10428ce3ac1afd49f63e3b01".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Dutch,
|
|
||||||
seed: "setwinst riphagen vimmetje extase blief tuitelig fuiven meifeest \
|
|
||||||
ponywagen zesmaal ripdeal matverf codetaal leut ivoor rotten \
|
|
||||||
wisgerhof winzucht typograaf atrium rein zilt traktaat verzaagd setwinst"
|
|
||||||
.into(),
|
|
||||||
spend: "e2d2873085c447c2bc7664222ac8f7d240df3aeac137f5ff2022eaa629e5b10a".into(),
|
|
||||||
view: "eac30b69477e3f68093d131c7fd961564458401b07f8c87ff8f6030c1a0c7301".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::French,
|
|
||||||
seed: "poids vaseux tarte bazar poivre effet entier nuance \
|
|
||||||
sensuel ennui pacte osselet poudre battre alibi mouton \
|
|
||||||
stade paquet pliage gibier type question position projet pliage"
|
|
||||||
.into(),
|
|
||||||
spend: "2dd39ff1a4628a94b5c2ec3e42fb3dfe15c2b2f010154dc3b3de6791e805b904".into(),
|
|
||||||
view: "6725b32230400a1032f31d622b44c3a227f88258939b14a7c72e00939e7bdf0e".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Spanish,
|
|
||||||
seed: "minero ocupar mirar evadir octubre cal logro miope \
|
|
||||||
opaco disco ancla litio clase cuello nasal clase \
|
|
||||||
fiar avance deseo mente grumo negro cordón croqueta clase"
|
|
||||||
.into(),
|
|
||||||
spend: "ae2c9bebdddac067d73ec0180147fc92bdf9ac7337f1bcafbbe57dd13558eb02".into(),
|
|
||||||
view: "18deafb34d55b7a43cae2c1c1c206a3c80c12cc9d1f84640b484b95b7fec3e05".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::German,
|
|
||||||
seed: "Kaliber Gabelung Tapir Liveband Favorit Specht Enklave Nabel \
|
|
||||||
Jupiter Foliant Chronik nisten löten Vase Aussage Rekord \
|
|
||||||
Yeti Gesetz Eleganz Alraune Künstler Almweide Jahr Kastanie Almweide"
|
|
||||||
.into(),
|
|
||||||
spend: "79801b7a1b9796856e2397d862a113862e1fdc289a205e79d8d70995b276db06".into(),
|
|
||||||
view: "99f0ec556643bd9c038a4ed86edcb9c6c16032c4622ed2e000299d527a792701".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Italian,
|
|
||||||
seed: "cavo pancetta auto fulmine alleanza filmato diavolo prato \
|
|
||||||
forzare meritare litigare lezione segreto evasione votare buio \
|
|
||||||
licenza cliente dorso natale crescere vento tutelare vetta evasione"
|
|
||||||
.into(),
|
|
||||||
spend: "5e7fd774eb00fa5877e2a8b4dc9c7ffe111008a3891220b56a6e49ac816d650a".into(),
|
|
||||||
view: "698a1dce6018aef5516e82ca0cb3e3ec7778d17dfb41a137567bfa2e55e63a03".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Portuguese,
|
|
||||||
seed: "agito eventualidade onus itrio holograma sodomizar objetos dobro \
|
|
||||||
iugoslavo bcrepuscular odalisca abjeto iuane darwinista eczema acetona \
|
|
||||||
cibernetico hoquei gleba driver buffer azoto megera nogueira agito"
|
|
||||||
.into(),
|
|
||||||
spend: "13b3115f37e35c6aa1db97428b897e584698670c1b27854568d678e729200c0f".into(),
|
|
||||||
view: "ad1b4fd35270f5f36c4da7166672b347e75c3f4d41346ec2a06d1d0193632801".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Japanese,
|
|
||||||
seed: "ぜんぶ どうぐ おたがい せんきょ おうじ そんちょう じゅしん いろえんぴつ \
|
|
||||||
かほう つかれる えらぶ にちじょう くのう にちようび ぬまえび さんきゃく \
|
|
||||||
おおや ちぬき うすめる いがく せつでん さうな すいえい せつだん おおや"
|
|
||||||
.into(),
|
|
||||||
spend: "c56e895cdb13007eda8399222974cdbab493640663804b93cbef3d8c3df80b0b".into(),
|
|
||||||
view: "6c3634a313ec2ee979d565c33888fd7c3502d696ce0134a8bc1a2698c7f2c508".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Russian,
|
|
||||||
seed: "шатер икра нация ехать получать инерция доза реальный \
|
|
||||||
рыжий таможня лопата душа веселый клетка атлас лекция \
|
|
||||||
обгонять паек наивный лыжный дурак стать ежик задача паек"
|
|
||||||
.into(),
|
|
||||||
spend: "7cb5492df5eb2db4c84af20766391cd3e3662ab1a241c70fc881f3d02c381f05".into(),
|
|
||||||
view: "fcd53e41ec0df995ab43927f7c44bc3359c93523d5009fb3f5ba87431d545a03".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Esperanto,
|
|
||||||
seed: "ukazo klini peco etikedo fabriko imitado onklino urino \
|
|
||||||
pudro incidento kumuluso ikono smirgi hirundo uretro krii \
|
|
||||||
sparkado super speciala pupo alpinisto cvana vokegi zombio fabriko"
|
|
||||||
.into(),
|
|
||||||
spend: "82ebf0336d3b152701964ed41df6b6e9a035e57fc98b84039ed0bd4611c58904".into(),
|
|
||||||
view: "cd4d120e1ea34360af528f6a3e6156063312d9cefc9aa6b5218d366c0ed6a201".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Lojban,
|
|
||||||
seed: "jetnu vensa julne xrotu xamsi julne cutci dakli \
|
|
||||||
mlatu xedja muvgau palpi xindo sfubu ciste cinri \
|
|
||||||
blabi darno dembi janli blabi fenki bukpu burcu blabi"
|
|
||||||
.into(),
|
|
||||||
spend: "e4f8c6819ab6cf792cebb858caabac9307fd646901d72123e0367ebc0a79c200".into(),
|
|
||||||
view: "c806ce62bafaa7b2d597f1a1e2dbe4a2f96bfd804bf6f8420fc7f4a6bd700c00".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::DeprecatedEnglish,
|
|
||||||
seed: "glorious especially puff son moment add youth nowhere \
|
|
||||||
throw glide grip wrong rhythm consume very swear \
|
|
||||||
bitter heavy eventually begin reason flirt type unable"
|
|
||||||
.into(),
|
|
||||||
spend: "647f4765b66b636ff07170ab6280a9a6804dfbaf19db2ad37d23be024a18730b".into(),
|
|
||||||
view: "045da65316a906a8c30046053119c18020b07a7a3a6ef5c01ab2a8755416bd02".into(),
|
|
||||||
},
|
|
||||||
// The following seeds require the language specification in order to calculate
|
|
||||||
// a single valid checksum
|
|
||||||
Vector {
|
|
||||||
language: Language::Spanish,
|
|
||||||
seed: "pluma laico atraer pintor peor cerca balde buscar \
|
|
||||||
lancha batir nulo reloj resto gemelo nevera poder columna gol \
|
|
||||||
oveja latir amplio bolero feliz fuerza nevera"
|
|
||||||
.into(),
|
|
||||||
spend: "30303983fc8d215dd020cc6b8223793318d55c466a86e4390954f373fdc7200a".into(),
|
|
||||||
view: "97c649143f3c147ba59aa5506cc09c7992c5c219bb26964442142bf97980800e".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Spanish,
|
|
||||||
seed: "pluma pluma pluma pluma pluma pluma pluma pluma \
|
|
||||||
pluma pluma pluma pluma pluma pluma pluma pluma \
|
|
||||||
pluma pluma pluma pluma pluma pluma pluma pluma pluma"
|
|
||||||
.into(),
|
|
||||||
spend: "b4050000b4050000b4050000b4050000b4050000b4050000b4050000b4050000".into(),
|
|
||||||
view: "d73534f7912b395eb70ef911791a2814eb6df7ce56528eaaa83ff2b72d9f5e0f".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::English,
|
|
||||||
seed: "plus plus plus plus plus plus plus plus \
|
|
||||||
plus plus plus plus plus plus plus plus \
|
|
||||||
plus plus plus plus plus plus plus plus plus"
|
|
||||||
.into(),
|
|
||||||
spend: "3b0400003b0400003b0400003b0400003b0400003b0400003b0400003b040000".into(),
|
|
||||||
view: "43a8a7715eed11eff145a2024ddcc39740255156da7bbd736ee66a0838053a02".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::Spanish,
|
|
||||||
seed: "audio audio audio audio audio audio audio audio \
|
|
||||||
audio audio audio audio audio audio audio audio \
|
|
||||||
audio audio audio audio audio audio audio audio audio"
|
|
||||||
.into(),
|
|
||||||
spend: "ba000000ba000000ba000000ba000000ba000000ba000000ba000000ba000000".into(),
|
|
||||||
view: "1437256da2c85d029b293d8c6b1d625d9374969301869b12f37186e3f906c708".into(),
|
|
||||||
},
|
|
||||||
Vector {
|
|
||||||
language: Language::English,
|
|
||||||
seed: "audio audio audio audio audio audio audio audio \
|
|
||||||
audio audio audio audio audio audio audio audio \
|
|
||||||
audio audio audio audio audio audio audio audio audio"
|
|
||||||
.into(),
|
|
||||||
spend: "7900000079000000790000007900000079000000790000007900000079000000".into(),
|
|
||||||
view: "20bec797ab96780ae6a045dd816676ca7ed1d7c6773f7022d03ad234b581d600".into(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
for vector in vectors {
|
|
||||||
fn trim_by_lang(word: &str, lang: Language) -> String {
|
|
||||||
if lang != Language::DeprecatedEnglish {
|
|
||||||
word.chars().take(LANGUAGES[&lang].unique_prefix_length).collect()
|
|
||||||
} else {
|
|
||||||
word.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let trim_seed = |seed: &str| {
|
|
||||||
seed
|
|
||||||
.split_whitespace()
|
|
||||||
.map(|word| trim_by_lang(word, vector.language))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(" ")
|
|
||||||
};
|
|
||||||
|
|
||||||
// Test against Monero
|
|
||||||
{
|
|
||||||
println!("{}. language: {:?}, seed: {}", line!(), vector.language, vector.seed.clone());
|
|
||||||
let seed = Seed::from_string(vector.language, Zeroizing::new(vector.seed.clone())).unwrap();
|
|
||||||
let trim = trim_seed(&vector.seed);
|
|
||||||
assert_eq!(seed, Seed::from_string(vector.language, Zeroizing::new(trim)).unwrap());
|
|
||||||
|
|
||||||
let spend: [u8; 32] = hex::decode(vector.spend).unwrap().try_into().unwrap();
|
|
||||||
// For originalal seeds, Monero directly uses the entropy as a spend key
|
|
||||||
assert_eq!(
|
|
||||||
Option::<Scalar>::from(Scalar::from_canonical_bytes(*seed.entropy())),
|
|
||||||
Option::<Scalar>::from(Scalar::from_canonical_bytes(spend)),
|
|
||||||
);
|
|
||||||
|
|
||||||
let view: [u8; 32] = hex::decode(vector.view).unwrap().try_into().unwrap();
|
|
||||||
// Monero then derives the view key as H(spend)
|
|
||||||
assert_eq!(
|
|
||||||
Scalar::from_bytes_mod_order(keccak256(spend)),
|
|
||||||
Scalar::from_canonical_bytes(view).unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(Seed::from_entropy(vector.language, Zeroizing::new(spend)).unwrap(), seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test against ourselves
|
|
||||||
{
|
|
||||||
let seed = Seed::new(&mut OsRng, vector.language);
|
|
||||||
println!("{}. seed: {}", line!(), *seed.to_string());
|
|
||||||
let trim = trim_seed(&seed.to_string());
|
|
||||||
assert_eq!(seed, Seed::from_string(vector.language, Zeroizing::new(trim)).unwrap());
|
|
||||||
assert_eq!(seed, Seed::from_entropy(vector.language, seed.entropy()).unwrap());
|
|
||||||
assert_eq!(seed, Seed::from_string(vector.language, seed.to_string()).unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -302,7 +302,8 @@ impl Extra {
|
|||||||
// `fill_buf` returns the current buffer, filled if empty, only empty if the reader is
|
// `fill_buf` returns the current buffer, filled if empty, only empty if the reader is
|
||||||
// exhausted
|
// exhausted
|
||||||
while !r.fill_buf()?.is_empty() {
|
while !r.fill_buf()?.is_empty() {
|
||||||
res.0.push(ExtraField::read(r)?);
|
let Ok(field) = ExtraField::read(r) else { break };
|
||||||
|
res.0.push(field);
|
||||||
}
|
}
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ pub(crate) mod output;
|
|||||||
pub use output::WalletOutput;
|
pub use output::WalletOutput;
|
||||||
|
|
||||||
mod scan;
|
mod scan;
|
||||||
pub use scan::{ScanError, Scanner, GuaranteedScanner};
|
pub use scan::{Timelocked, ScanError, Scanner, GuaranteedScanner};
|
||||||
|
|
||||||
mod decoys;
|
mod decoys;
|
||||||
pub use decoys::OutputWithDecoys;
|
pub use decoys::OutputWithDecoys;
|
||||||
@@ -137,15 +137,13 @@ impl SharedKeyDerivations {
|
|||||||
|
|
||||||
fn decrypt(&self, enc_amount: &EncryptedAmount) -> Commitment {
|
fn decrypt(&self, enc_amount: &EncryptedAmount) -> Commitment {
|
||||||
match enc_amount {
|
match enc_amount {
|
||||||
// TODO: Add a test vector for this
|
|
||||||
EncryptedAmount::Original { mask, amount } => {
|
EncryptedAmount::Original { mask, amount } => {
|
||||||
let mask_shared_sec = keccak256(self.shared_key.as_bytes());
|
let mask_shared_sec_scalar = keccak256_to_scalar(self.shared_key.as_bytes());
|
||||||
let mask =
|
let amount_shared_sec_scalar = keccak256_to_scalar(mask_shared_sec_scalar.as_bytes());
|
||||||
Scalar::from_bytes_mod_order(*mask) - Scalar::from_bytes_mod_order(mask_shared_sec);
|
|
||||||
|
let mask = Scalar::from_bytes_mod_order(*mask) - mask_shared_sec_scalar;
|
||||||
|
let amount_scalar = Scalar::from_bytes_mod_order(*amount) - amount_shared_sec_scalar;
|
||||||
|
|
||||||
let amount_shared_sec = keccak256(mask_shared_sec);
|
|
||||||
let amount_scalar =
|
|
||||||
Scalar::from_bytes_mod_order(*amount) - Scalar::from_bytes_mod_order(amount_shared_sec);
|
|
||||||
// d2b from rctTypes.cpp
|
// d2b from rctTypes.cpp
|
||||||
let amount = u64::from_le_bytes(amount_scalar.to_bytes()[0 .. 8].try_into().unwrap());
|
let amount = u64::from_le_bytes(amount_scalar.to_bytes()[0 .. 8].try_into().unwrap());
|
||||||
|
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ impl Timelocked {
|
|||||||
///
|
///
|
||||||
/// `block` is the block number of the block the additional timelock must be satsified by.
|
/// `block` is the block number of the block the additional timelock must be satsified by.
|
||||||
///
|
///
|
||||||
/// `time` is represented in seconds since the epoch. Please note Monero uses an on-chain
|
/// `time` is represented in seconds since the epoch and is in terms of Monero's on-chain clock.
|
||||||
/// deterministic clock for time which is subject to variance from the real world time. This time
|
/// That means outputs whose additional timelocks are statisfied by `Instant::now()` (the time
|
||||||
/// argument will be evaluated against Monero's clock, not the local system's clock.
|
/// according to the local system clock) may still be locked due to variance with Monero's clock.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn additional_timelock_satisfied_by(self, block: usize, time: u64) -> Vec<WalletOutput> {
|
pub fn additional_timelock_satisfied_by(self, block: usize, time: u64) -> Vec<WalletOutput> {
|
||||||
let mut res = vec![];
|
let mut res = vec![];
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
mod tx_keys;
|
mod tx_keys;
|
||||||
|
pub use tx_keys::TransactionKeys;
|
||||||
mod tx;
|
mod tx;
|
||||||
mod eventuality;
|
mod eventuality;
|
||||||
pub use eventuality::Eventuality;
|
pub use eventuality::Eventuality;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use std_shims::{vec, vec::Vec};
|
use std_shims::{vec, vec::Vec};
|
||||||
|
|
||||||
use zeroize::Zeroizing;
|
use zeroize::{Zeroize, Zeroizing};
|
||||||
|
|
||||||
use rand_core::SeedableRng;
|
use rand_core::SeedableRng;
|
||||||
use rand_chacha::ChaCha20Rng;
|
use rand_chacha::ChaCha20Rng;
|
||||||
@@ -15,28 +15,61 @@ use crate::{
|
|||||||
send::{ChangeEnum, InternalPayment, SignableTransaction, key_image_sort},
|
send::{ChangeEnum, InternalPayment, SignableTransaction, key_image_sort},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn seeded_rng(
|
||||||
|
dst: &'static [u8],
|
||||||
|
outgoing_view_key: &[u8; 32],
|
||||||
|
mut input_keys: Vec<EdwardsPoint>,
|
||||||
|
) -> ChaCha20Rng {
|
||||||
|
// Apply the DST
|
||||||
|
let mut transcript = Zeroizing::new(vec![u8::try_from(dst.len()).unwrap()]);
|
||||||
|
transcript.extend(dst);
|
||||||
|
|
||||||
|
// Bind to the outgoing view key to prevent foreign entities from rebuilding the transcript
|
||||||
|
transcript.extend(outgoing_view_key);
|
||||||
|
|
||||||
|
// We sort the inputs here to ensure a consistent order
|
||||||
|
// We use the key image sort as it's applicable and well-defined, not because these are key
|
||||||
|
// images
|
||||||
|
input_keys.sort_by(key_image_sort);
|
||||||
|
|
||||||
|
// Ensure uniqueness across transactions by binding to a use-once object
|
||||||
|
// The keys for the inputs is binding to their key images, making them use-once
|
||||||
|
for key in input_keys {
|
||||||
|
transcript.extend(key.compress().to_bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = ChaCha20Rng::from_seed(keccak256(&transcript));
|
||||||
|
transcript.zeroize();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An iterator yielding an endless amount of ephemeral keys to use within a transaction.
|
||||||
|
///
|
||||||
|
/// This is used when sending and can be used after sending to re-derive the keys used, as
|
||||||
|
/// necessary for payment proofs.
|
||||||
|
pub struct TransactionKeys(ChaCha20Rng);
|
||||||
|
impl TransactionKeys {
|
||||||
|
/// Construct a new `TransactionKeys`.
|
||||||
|
///
|
||||||
|
/// `input_keys` is the list of keys from the outputs spent within this transaction.
|
||||||
|
pub fn new(outgoing_view_key: &Zeroizing<[u8; 32]>, input_keys: Vec<EdwardsPoint>) -> Self {
|
||||||
|
Self(seeded_rng(b"transaction_keys", outgoing_view_key, input_keys))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Iterator for TransactionKeys {
|
||||||
|
type Item = Zeroizing<Scalar>;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
Some(Zeroizing::new(Scalar::random(&mut self.0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl SignableTransaction {
|
impl SignableTransaction {
|
||||||
|
fn input_keys(&self) -> Vec<EdwardsPoint> {
|
||||||
|
self.inputs.iter().map(OutputWithDecoys::key).collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn seeded_rng(&self, dst: &'static [u8]) -> ChaCha20Rng {
|
pub(crate) fn seeded_rng(&self, dst: &'static [u8]) -> ChaCha20Rng {
|
||||||
// Apply the DST
|
seeded_rng(dst, &self.outgoing_view_key, self.input_keys())
|
||||||
let mut transcript = Zeroizing::new(vec![u8::try_from(dst.len()).unwrap()]);
|
|
||||||
transcript.extend(dst);
|
|
||||||
|
|
||||||
// Bind to the outgoing view key to prevent foreign entities from rebuilding the transcript
|
|
||||||
transcript.extend(self.outgoing_view_key.as_slice());
|
|
||||||
|
|
||||||
// Ensure uniqueness across transactions by binding to a use-once object
|
|
||||||
// The keys for the inputs is binding to their key images, making them use-once
|
|
||||||
let mut input_keys = self.inputs.iter().map(OutputWithDecoys::key).collect::<Vec<_>>();
|
|
||||||
// We sort the inputs mid-way through TX construction, so apply our own sort to ensure a
|
|
||||||
// consistent order
|
|
||||||
// We use the key image sort as it's applicable and well-defined, not because these are key
|
|
||||||
// images
|
|
||||||
input_keys.sort_by(key_image_sort);
|
|
||||||
for key in input_keys {
|
|
||||||
transcript.extend(key.compress().to_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
ChaCha20Rng::from_seed(keccak256(&transcript))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_payments_to_subaddresses(&self) -> bool {
|
fn has_payments_to_subaddresses(&self) -> bool {
|
||||||
@@ -81,14 +114,14 @@ impl SignableTransaction {
|
|||||||
|
|
||||||
// Calculate the transaction keys used as randomness.
|
// Calculate the transaction keys used as randomness.
|
||||||
fn transaction_keys(&self) -> (Zeroizing<Scalar>, Vec<Zeroizing<Scalar>>) {
|
fn transaction_keys(&self) -> (Zeroizing<Scalar>, Vec<Zeroizing<Scalar>>) {
|
||||||
let mut rng = self.seeded_rng(b"transaction_keys");
|
let mut tx_keys = TransactionKeys::new(&self.outgoing_view_key, self.input_keys());
|
||||||
|
|
||||||
let tx_key = Zeroizing::new(Scalar::random(&mut rng));
|
let tx_key = tx_keys.next().unwrap();
|
||||||
|
|
||||||
let mut additional_keys = vec![];
|
let mut additional_keys = vec![];
|
||||||
if self.should_use_additional_keys() {
|
if self.should_use_additional_keys() {
|
||||||
for _ in 0 .. self.payments.len() {
|
for _ in 0 .. self.payments.len() {
|
||||||
additional_keys.push(Zeroizing::new(Scalar::random(&mut rng)));
|
additional_keys.push(tx_keys.next().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(tx_key, additional_keys)
|
(tx_key, additional_keys)
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
mod extra;
|
mod extra;
|
||||||
|
mod scan;
|
||||||
|
|||||||
168
networks/monero/wallet/src/tests/scan.rs
Normal file
168
networks/monero/wallet/src/tests/scan.rs
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
use monero_rpc::ScannableBlock;
|
||||||
|
use crate::{
|
||||||
|
transaction::{Pruned, Transaction},
|
||||||
|
block::Block,
|
||||||
|
ViewPair, Scanner, WalletOutput,
|
||||||
|
output::{AbsoluteId, RelativeId, OutputData, Metadata},
|
||||||
|
Commitment,
|
||||||
|
PaymentId::Encrypted,
|
||||||
|
transaction::Timelock,
|
||||||
|
ringct::EncryptedAmount,
|
||||||
|
};
|
||||||
|
use zeroize::Zeroizing;
|
||||||
|
use curve25519_dalek::{Scalar, constants::ED25519_BASEPOINT_TABLE, edwards::CompressedEdwardsY};
|
||||||
|
|
||||||
|
const SPEND_KEY: &str = "ccf0ea10e1ea64354f42fa710c2b318e581969cf49046d809d1f0aadb3fc7a02";
|
||||||
|
const VIEW_KEY: &str = "a28b4b2085592881df94ee95da332c16b5bb773eb8bb74730208cbb236c73806";
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const PRUNED_TX_WITH_LONG_ENCRYPTED_AMOUNT: &str = "020001020003060101cf60390bb71aa15eb24037772012d59dc68cb4b6211e1c93206db09a6c346261020002ee8ca293511571c0005e1c144e49d09b8ff03046dbafb3e064a34cb9fc1994b600029e2e5cd08c8681dbcf2ce66071467e835f7e86613fbfed3c4fb170127b94e1072c01d3ce2a622c6e06ed465f81017dd6188c3a6e3d8e65a846f9c98416da0e150a82020901c553d35e54111bd001e0bbcbf289d701ce90e309ead2b487ec1d4d8af5d649543eb99a7620f6b54e532898527be29704f050e6f06de61e5967b2ddd506b4d6d36546065d6aae156ac7bec18c99580c07867fb98cb29853edbafec91af2df605c12f9aaa81a9165625afb6649f5a652012c5ba6612351140e1fb4a8463cc765d0a9bb7d999ba35750f365c5285d77230b76c7a612784f4845812a2899f2ca6a304fee61362db59b263115c27d2ce78af6b1d9e939c1f4036c7707851f41abe6458cf1c748353e593469ebf43536a939f7";
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
const BLOCK: &str = "0202e8e28efe04db09e2fc4d57854786220bd33e0169ff692440d27ae3932b9219df9ab1d7260b00000000014101ff050580d0acf30e02704972eb1878e94686b62fa4c0202f3e7e3a263073bd6edd751990ea769494ee80c0fc82aa0202edac72ab7c5745d4acaa95f76a3b76e238a55743cd51efb586f968e09821788d80d0dbc3f40202f9b4cf3141aac4203a1aaed01f09326615544997d1b68964928d9aafd07e38e580a0e5b9c29101023405e3aa75b1b7adf04e8c7faa3c3d45616ae740a8b11fb7cc1555dd8b9e4c9180c0dfda8ee90602d2b78accfe1c2ae57bed4fe3385f7735a988f160ef3bbc1f9d7a0c911c26ffd92101d2d55b5066d247a97696be4a84bf70873e4f149687f57e606eb6682f11650e1701b74773bbea995079805398052da9b69244bda034b089b50e4d9151dedb59a12f";
|
||||||
|
|
||||||
|
const OUTPUT_INDEX_FOR_FIRST_RINGCT_OUTPUT: u64 = 0; // note the miner tx is a v1 tx
|
||||||
|
|
||||||
|
fn wallet_output0() -> WalletOutput {
|
||||||
|
WalletOutput {
|
||||||
|
absolute_id: AbsoluteId {
|
||||||
|
transaction: hex::decode("b74773bbea995079805398052da9b69244bda034b089b50e4d9151dedb59a12f")
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
index_in_transaction: 0,
|
||||||
|
},
|
||||||
|
relative_id: RelativeId { index_on_blockchain: OUTPUT_INDEX_FOR_FIRST_RINGCT_OUTPUT },
|
||||||
|
data: OutputData {
|
||||||
|
key: CompressedEdwardsY(
|
||||||
|
hex::decode("ee8ca293511571c0005e1c144e49d09b8ff03046dbafb3e064a34cb9fc1994b6")
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.decompress()
|
||||||
|
.unwrap(),
|
||||||
|
key_offset: Scalar::from_canonical_bytes(
|
||||||
|
hex::decode("f1d21a76ea0bb228fbc5f0dece0597a8ffb59de7a04b29f70b7c0310446ea905")
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
commitment: Commitment {
|
||||||
|
amount: 10000,
|
||||||
|
mask: Scalar::from_canonical_bytes(
|
||||||
|
hex::decode("05c2f142aaf3054cbff0a022f6c7cb75403fd92af0f9441c072ade3f273f7706")
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metadata: Metadata {
|
||||||
|
additional_timelock: Timelock::None,
|
||||||
|
subaddress: None,
|
||||||
|
payment_id: Some(Encrypted([0, 0, 0, 0, 0, 0, 0, 0])),
|
||||||
|
arbitrary_data: [].to_vec(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wallet_output1() -> WalletOutput {
|
||||||
|
WalletOutput {
|
||||||
|
absolute_id: AbsoluteId {
|
||||||
|
transaction: hex::decode("b74773bbea995079805398052da9b69244bda034b089b50e4d9151dedb59a12f")
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
index_in_transaction: 1,
|
||||||
|
},
|
||||||
|
relative_id: RelativeId { index_on_blockchain: OUTPUT_INDEX_FOR_FIRST_RINGCT_OUTPUT + 1 },
|
||||||
|
data: OutputData {
|
||||||
|
key: CompressedEdwardsY(
|
||||||
|
hex::decode("9e2e5cd08c8681dbcf2ce66071467e835f7e86613fbfed3c4fb170127b94e107")
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.decompress()
|
||||||
|
.unwrap(),
|
||||||
|
key_offset: Scalar::from_canonical_bytes(
|
||||||
|
hex::decode("c5189738c1cb40e68d464f1a1848a85f6ab2c09652a31849213dc0fefd212806")
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
commitment: Commitment {
|
||||||
|
amount: 10000,
|
||||||
|
mask: Scalar::from_canonical_bytes(
|
||||||
|
hex::decode("c8922ce32cb2bf454a6b77bc91423ba7a18412b71fa39a97a2a743c1fe0bad04")
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
metadata: Metadata {
|
||||||
|
additional_timelock: Timelock::None,
|
||||||
|
subaddress: None,
|
||||||
|
payment_id: Some(Encrypted([0, 0, 0, 0, 0, 0, 0, 0])),
|
||||||
|
arbitrary_data: [].to_vec(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scan_long_encrypted_amount() {
|
||||||
|
// Parse strings
|
||||||
|
let spend_key_buf = hex::decode(SPEND_KEY).unwrap();
|
||||||
|
let spend_key =
|
||||||
|
Zeroizing::new(Scalar::from_canonical_bytes(spend_key_buf.try_into().unwrap()).unwrap());
|
||||||
|
|
||||||
|
let view_key_buf = hex::decode(VIEW_KEY).unwrap();
|
||||||
|
let view_key =
|
||||||
|
Zeroizing::new(Scalar::from_canonical_bytes(view_key_buf.try_into().unwrap()).unwrap());
|
||||||
|
|
||||||
|
let tx_buf = hex::decode(PRUNED_TX_WITH_LONG_ENCRYPTED_AMOUNT).unwrap();
|
||||||
|
let tx = Transaction::<Pruned>::read::<&[u8]>(&mut tx_buf.as_ref()).unwrap();
|
||||||
|
|
||||||
|
let block_buf = hex::decode(BLOCK).unwrap();
|
||||||
|
let block = Block::read::<&[u8]>(&mut block_buf.as_ref()).unwrap();
|
||||||
|
|
||||||
|
// Confirm tx has long form encrypted amounts
|
||||||
|
match &tx {
|
||||||
|
Transaction::V2 { prefix: _, proofs } => {
|
||||||
|
let proofs = proofs.clone().unwrap();
|
||||||
|
assert_eq!(proofs.base.encrypted_amounts.len(), 2);
|
||||||
|
assert!(proofs
|
||||||
|
.base
|
||||||
|
.encrypted_amounts
|
||||||
|
.iter()
|
||||||
|
.all(|o| matches!(o, EncryptedAmount::Original { .. })));
|
||||||
|
}
|
||||||
|
_ => panic!("Unexpected tx version"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prepare scanner
|
||||||
|
let spend_pub = &*spend_key * ED25519_BASEPOINT_TABLE;
|
||||||
|
let view: ViewPair = ViewPair::new(spend_pub, view_key).unwrap();
|
||||||
|
let mut scanner = Scanner::new(view);
|
||||||
|
|
||||||
|
// Prepare scannable block
|
||||||
|
let txs: Vec<Transaction<Pruned>> = vec![tx];
|
||||||
|
let scannable_block = ScannableBlock {
|
||||||
|
block,
|
||||||
|
transactions: txs,
|
||||||
|
output_index_for_first_ringct_output: Some(OUTPUT_INDEX_FOR_FIRST_RINGCT_OUTPUT),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Scan the block
|
||||||
|
let outputs = scanner.scan(scannable_block).unwrap().not_additionally_locked();
|
||||||
|
|
||||||
|
assert_eq!(outputs.len(), 2);
|
||||||
|
assert_eq!(outputs[0], wallet_output0());
|
||||||
|
assert_eq!(outputs[1], wallet_output1());
|
||||||
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "monero-wallet-util"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Additional utility functions for monero-wallet"
|
|
||||||
license = "MIT"
|
|
||||||
repository = "https://github.com/serai-dex/serai/tree/develop/networks/monero/wallet/util"
|
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
|
||||||
edition = "2021"
|
|
||||||
rust-version = "1.80"
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
all-features = true
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
std-shims = { path = "../../../../common/std-shims", version = "^0.1.1", default-features = false }
|
|
||||||
|
|
||||||
thiserror = { version = "2", default-features = false }
|
|
||||||
|
|
||||||
zeroize = { version = "^1.5", default-features = false, features = ["zeroize_derive"] }
|
|
||||||
rand_core = { version = "0.6", default-features = false }
|
|
||||||
|
|
||||||
monero-wallet = { path = "..", default-features = false }
|
|
||||||
|
|
||||||
monero-seed = { path = "../seed", default-features = false }
|
|
||||||
polyseed = { path = "../polyseed", default-features = false }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
hex = { version = "0.4", default-features = false, features = ["std"] }
|
|
||||||
curve25519-dalek = { version = "4", default-features = false, features = ["alloc", "zeroize"] }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
std = [
|
|
||||||
"std-shims/std",
|
|
||||||
|
|
||||||
"thiserror/std",
|
|
||||||
|
|
||||||
"zeroize/std",
|
|
||||||
"rand_core/std",
|
|
||||||
|
|
||||||
"monero-wallet/std",
|
|
||||||
|
|
||||||
"monero-seed/std",
|
|
||||||
"polyseed/std",
|
|
||||||
]
|
|
||||||
compile-time-generators = ["monero-wallet/compile-time-generators"]
|
|
||||||
multisig = ["monero-wallet/multisig", "std"]
|
|
||||||
default = ["std", "compile-time-generators"]
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2022-2024 Luke Parker
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# Monero Wallet Utilities
|
|
||||||
|
|
||||||
Additional utility functions for monero-wallet.
|
|
||||||
|
|
||||||
This library is isolated as it adds a notable amount of dependencies to the
|
|
||||||
tree, and to be a subject to a distinct versioning policy. This library may
|
|
||||||
more frequently undergo breaking API changes.
|
|
||||||
|
|
||||||
This library is usable under no-std when the `std` feature (on by default) is
|
|
||||||
disabled.
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- Support for Monero's seed algorithm
|
|
||||||
- Support for Polyseed
|
|
||||||
|
|
||||||
### Cargo Features
|
|
||||||
|
|
||||||
- `std` (on by default): Enables `std` (and with it, more efficient internal
|
|
||||||
implementations).
|
|
||||||
- `compile-time-generators` (on by default): Derives the generators at
|
|
||||||
compile-time so they don't need to be derived at runtime. This is recommended
|
|
||||||
if program size doesn't need to be kept minimal.
|
|
||||||
- `multisig`: Adds support for creation of transactions using a threshold
|
|
||||||
multisignature wallet.
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
||||||
#![doc = include_str!("../README.md")]
|
|
||||||
#![deny(missing_docs)]
|
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
|
||||||
|
|
||||||
pub use monero_wallet::*;
|
|
||||||
|
|
||||||
/// Seed creation and parsing functionality.
|
|
||||||
pub mod seed;
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
use core::fmt;
|
|
||||||
use std_shims::string::String;
|
|
||||||
|
|
||||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
|
||||||
use rand_core::{RngCore, CryptoRng};
|
|
||||||
|
|
||||||
pub use monero_seed as original;
|
|
||||||
pub use polyseed;
|
|
||||||
|
|
||||||
use original::{SeedError as OriginalSeedError, Seed as OriginalSeed};
|
|
||||||
use polyseed::{PolyseedError, Polyseed};
|
|
||||||
|
|
||||||
/// An error from working with seeds.
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, thiserror::Error)]
|
|
||||||
pub enum SeedError {
|
|
||||||
/// The seed was invalid.
|
|
||||||
#[error("invalid seed")]
|
|
||||||
InvalidSeed,
|
|
||||||
/// The entropy was invalid.
|
|
||||||
#[error("invalid entropy")]
|
|
||||||
InvalidEntropy,
|
|
||||||
/// The checksum did not match the data.
|
|
||||||
#[error("invalid checksum")]
|
|
||||||
InvalidChecksum,
|
|
||||||
/// Unsupported features were enabled.
|
|
||||||
#[error("unsupported features")]
|
|
||||||
UnsupportedFeatures,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<OriginalSeedError> for SeedError {
|
|
||||||
fn from(error: OriginalSeedError) -> SeedError {
|
|
||||||
match error {
|
|
||||||
OriginalSeedError::DeprecatedEnglishWithChecksum | OriginalSeedError::InvalidChecksum => {
|
|
||||||
SeedError::InvalidChecksum
|
|
||||||
}
|
|
||||||
OriginalSeedError::InvalidSeed => SeedError::InvalidSeed,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<PolyseedError> for SeedError {
|
|
||||||
fn from(error: PolyseedError) -> SeedError {
|
|
||||||
match error {
|
|
||||||
PolyseedError::UnsupportedFeatures => SeedError::UnsupportedFeatures,
|
|
||||||
PolyseedError::InvalidEntropy => SeedError::InvalidEntropy,
|
|
||||||
PolyseedError::InvalidSeed => SeedError::InvalidSeed,
|
|
||||||
PolyseedError::InvalidChecksum => SeedError::InvalidChecksum,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The type of the seed.
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
||||||
pub enum SeedType {
|
|
||||||
/// The seed format originally used by Monero,
|
|
||||||
Original(monero_seed::Language),
|
|
||||||
/// Polyseed.
|
|
||||||
Polyseed(polyseed::Language),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A seed, internally either the original format or a Polyseed.
|
|
||||||
#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
|
||||||
pub enum Seed {
|
|
||||||
/// The originally formatted seed.
|
|
||||||
Original(OriginalSeed),
|
|
||||||
/// A Polyseed.
|
|
||||||
Polyseed(Polyseed),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for Seed {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Seed::Original(_) => f.debug_struct("Seed::Original").finish_non_exhaustive(),
|
|
||||||
Seed::Polyseed(_) => f.debug_struct("Seed::Polyseed").finish_non_exhaustive(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Seed {
|
|
||||||
/// Create a new seed.
|
|
||||||
pub fn new<R: RngCore + CryptoRng>(rng: &mut R, seed_type: SeedType) -> Seed {
|
|
||||||
match seed_type {
|
|
||||||
SeedType::Original(lang) => Seed::Original(OriginalSeed::new(rng, lang)),
|
|
||||||
SeedType::Polyseed(lang) => Seed::Polyseed(Polyseed::new(rng, lang)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parse a seed from a string.
|
|
||||||
pub fn from_string(seed_type: SeedType, words: Zeroizing<String>) -> Result<Seed, SeedError> {
|
|
||||||
match seed_type {
|
|
||||||
SeedType::Original(lang) => Ok(OriginalSeed::from_string(lang, words).map(Seed::Original)?),
|
|
||||||
SeedType::Polyseed(lang) => Ok(Polyseed::from_string(lang, words).map(Seed::Polyseed)?),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a seed from entropy.
|
|
||||||
///
|
|
||||||
/// A birthday may be optionally provided, denoted in seconds since the epoch. For
|
|
||||||
/// SeedType::Original, it will be ignored. For SeedType::Polyseed, it'll be embedded into the
|
|
||||||
/// seed.
|
|
||||||
///
|
|
||||||
/// For SeedType::Polyseed, the last 13 bytes of `entropy` must be 0.
|
|
||||||
// TODO: Return Result, not Option
|
|
||||||
pub fn from_entropy(
|
|
||||||
seed_type: SeedType,
|
|
||||||
entropy: Zeroizing<[u8; 32]>,
|
|
||||||
birthday: Option<u64>,
|
|
||||||
) -> Option<Seed> {
|
|
||||||
match seed_type {
|
|
||||||
SeedType::Original(lang) => OriginalSeed::from_entropy(lang, entropy).map(Seed::Original),
|
|
||||||
SeedType::Polyseed(lang) => {
|
|
||||||
Polyseed::from(lang, 0, birthday.unwrap_or(0), entropy).ok().map(Seed::Polyseed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts the seed to a string.
|
|
||||||
pub fn to_string(&self) -> Zeroizing<String> {
|
|
||||||
match self {
|
|
||||||
Seed::Original(seed) => seed.to_string(),
|
|
||||||
Seed::Polyseed(seed) => seed.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the entropy for this seed.
|
|
||||||
pub fn entropy(&self) -> Zeroizing<[u8; 32]> {
|
|
||||||
match self {
|
|
||||||
Seed::Original(seed) => seed.entropy(),
|
|
||||||
Seed::Polyseed(seed) => seed.entropy().clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the key derived from this seed.
|
|
||||||
pub fn key(&self) -> Zeroizing<[u8; 32]> {
|
|
||||||
match self {
|
|
||||||
// Original does not differentiate between its entropy and its key
|
|
||||||
Seed::Original(seed) => seed.entropy(),
|
|
||||||
Seed::Polyseed(seed) => seed.key(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the birthday of this seed, denoted in seconds since the epoch.
|
|
||||||
pub fn birthday(&self) -> u64 {
|
|
||||||
match self {
|
|
||||||
Seed::Original(_) => 0,
|
|
||||||
Seed::Polyseed(seed) => seed.birthday(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// TODO
|
|
||||||
#[test]
|
|
||||||
fn test() {}
|
|
||||||
24
patches/tiny-bip39/Cargo.toml
Normal file
24
patches/tiny-bip39/Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "tiny-bip39"
|
||||||
|
version = "1.0.2"
|
||||||
|
description = "tiny-bip39 which patches to the latest update"
|
||||||
|
license = "MIT"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/patches/tiny-bip39"
|
||||||
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
|
keywords = []
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.70"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[package.metadata.cargo-machete]
|
||||||
|
ignored = ["tiny-bip39"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "bip39"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tiny-bip39 = "2"
|
||||||
1
patches/tiny-bip39/src/lib.rs
Normal file
1
patches/tiny-bip39/src/lib.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub use bip39::*;
|
||||||
@@ -35,17 +35,17 @@ fn test_batch_signer() {
|
|||||||
let block = BlockHash([0xaa; 32]);
|
let block = BlockHash([0xaa; 32]);
|
||||||
|
|
||||||
let batch = Batch {
|
let batch = Batch {
|
||||||
network: NetworkId::Monero,
|
network: ExternalNetworkId::Monero,
|
||||||
id,
|
id,
|
||||||
block,
|
block,
|
||||||
instructions: vec![
|
instructions: vec![
|
||||||
InInstructionWithBalance {
|
InInstructionWithBalance {
|
||||||
instruction: InInstruction::Transfer(SeraiAddress([0xbb; 32])),
|
instruction: InInstruction::Transfer(SeraiAddress([0xbb; 32])),
|
||||||
balance: Balance { coin: Coin::Bitcoin, amount: Amount(1000) },
|
balance: ExternalBalance { coin: ExternalCoin::Bitcoin, amount: Amount(1000) },
|
||||||
},
|
},
|
||||||
InInstructionWithBalance {
|
InInstructionWithBalance {
|
||||||
instruction: InInstruction::Dex(DexCall::SwapAndAddLiquidity(SeraiAddress([0xbb; 32]))),
|
instruction: InInstruction::Dex(DexCall::SwapAndAddLiquidity(SeraiAddress([0xbb; 32]))),
|
||||||
balance: Balance { coin: Coin::Monero, amount: Amount(9999999999999999) },
|
balance: ExternalBalance { coin: ExternalCoin::Monero, amount: Amount(9999999999999999) },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -72,7 +72,7 @@ fn test_batch_signer() {
|
|||||||
let i = Participant::new(u16::try_from(i).unwrap()).unwrap();
|
let i = Participant::new(u16::try_from(i).unwrap()).unwrap();
|
||||||
let keys = keys.get(&i).unwrap().clone();
|
let keys = keys.get(&i).unwrap().clone();
|
||||||
|
|
||||||
let mut signer = BatchSigner::<MemDb>::new(NetworkId::Monero, Session(0), vec![keys]);
|
let mut signer = BatchSigner::<MemDb>::new(ExternalNetworkId::Monero, Session(0), vec![keys]);
|
||||||
let mut db = MemDb::new();
|
let mut db = MemDb::new();
|
||||||
|
|
||||||
let mut txn = db.txn();
|
let mut txn = db.txn();
|
||||||
|
|||||||
@@ -398,7 +398,7 @@ mod ethereum {
|
|||||||
let deployer = Deployer::new(provider.clone()).await.unwrap().unwrap();
|
let deployer = Deployer::new(provider.clone()).await.unwrap().unwrap();
|
||||||
|
|
||||||
let mut tx = deployer.deploy_router(&key);
|
let mut tx = deployer.deploy_router(&key);
|
||||||
tx.gas_limit = 1_000_000u64.into();
|
tx.gas_limit = 1_000_000u64;
|
||||||
tx.gas_price = 1_000_000_000u64.into();
|
tx.gas_price = 1_000_000_000u64.into();
|
||||||
let tx = ethereum_serai::crypto::deterministically_sign(&tx);
|
let tx = ethereum_serai::crypto::deterministically_sign(&tx);
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use frost::{
|
|||||||
use serai_db::{DbTxn, Db, MemDb};
|
use serai_db::{DbTxn, Db, MemDb};
|
||||||
|
|
||||||
use serai_client::{
|
use serai_client::{
|
||||||
primitives::{NetworkId, Coin, Amount, Balance},
|
primitives::{ExternalNetworkId, ExternalCoin, Amount, ExternalBalance},
|
||||||
validator_sets::primitives::Session,
|
validator_sets::primitives::Session,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -186,12 +186,11 @@ pub async fn test_signer<N: Network>(
|
|||||||
let mut scheduler = N::Scheduler::new::<MemDb>(&mut txn, key, N::NETWORK);
|
let mut scheduler = N::Scheduler::new::<MemDb>(&mut txn, key, N::NETWORK);
|
||||||
let payments = vec![Payment {
|
let payments = vec![Payment {
|
||||||
address: N::external_address(&network, key).await,
|
address: N::external_address(&network, key).await,
|
||||||
balance: Balance {
|
balance: ExternalBalance {
|
||||||
coin: match N::NETWORK {
|
coin: match N::NETWORK {
|
||||||
NetworkId::Serai => panic!("test_signer called with Serai"),
|
ExternalNetworkId::Bitcoin => ExternalCoin::Bitcoin,
|
||||||
NetworkId::Bitcoin => Coin::Bitcoin,
|
ExternalNetworkId::Ethereum => ExternalCoin::Ether,
|
||||||
NetworkId::Ethereum => Coin::Ether,
|
ExternalNetworkId::Monero => ExternalCoin::Monero,
|
||||||
NetworkId::Monero => Coin::Monero,
|
|
||||||
},
|
},
|
||||||
amount: Amount(amount),
|
amount: Amount(amount),
|
||||||
},
|
},
|
||||||
@@ -225,7 +224,7 @@ pub async fn test_signer<N: Network>(
|
|||||||
.await;
|
.await;
|
||||||
// Don't run if Ethereum as the received output will revert by the contract
|
// Don't run if Ethereum as the received output will revert by the contract
|
||||||
// (and therefore not actually exist)
|
// (and therefore not actually exist)
|
||||||
if N::NETWORK != NetworkId::Ethereum {
|
if N::NETWORK != ExternalNetworkId::Ethereum {
|
||||||
assert_eq!(outputs.len(), 1 + usize::from(u8::from(plan.change.is_some())));
|
assert_eq!(outputs.len(), 1 + usize::from(u8::from(plan.change.is_some())));
|
||||||
// Adjust the amount for the fees
|
// Adjust the amount for the fees
|
||||||
let amount = amount - tx.fee(&network).await;
|
let amount = amount - tx.fee(&network).await;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use tokio::time::timeout;
|
|||||||
use serai_db::{DbTxn, Db, MemDb};
|
use serai_db::{DbTxn, Db, MemDb};
|
||||||
|
|
||||||
use serai_client::{
|
use serai_client::{
|
||||||
primitives::{NetworkId, Coin, Amount, Balance},
|
primitives::{ExternalNetworkId, ExternalCoin, Amount, ExternalBalance},
|
||||||
validator_sets::primitives::Session,
|
validator_sets::primitives::Session,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -90,12 +90,11 @@ pub async fn test_wallet<N: Network>(
|
|||||||
outputs.clone(),
|
outputs.clone(),
|
||||||
vec![Payment {
|
vec![Payment {
|
||||||
address: N::external_address(&network, key).await,
|
address: N::external_address(&network, key).await,
|
||||||
balance: Balance {
|
balance: ExternalBalance {
|
||||||
coin: match N::NETWORK {
|
coin: match N::NETWORK {
|
||||||
NetworkId::Serai => panic!("test_wallet called with Serai"),
|
ExternalNetworkId::Bitcoin => ExternalCoin::Bitcoin,
|
||||||
NetworkId::Bitcoin => Coin::Bitcoin,
|
ExternalNetworkId::Ethereum => ExternalCoin::Ether,
|
||||||
NetworkId::Ethereum => Coin::Ether,
|
ExternalNetworkId::Monero => ExternalCoin::Monero,
|
||||||
NetworkId::Monero => Coin::Monero,
|
|
||||||
},
|
},
|
||||||
amount: Amount(amount),
|
amount: Amount(amount),
|
||||||
},
|
},
|
||||||
@@ -117,12 +116,11 @@ pub async fn test_wallet<N: Network>(
|
|||||||
plans[0].payments,
|
plans[0].payments,
|
||||||
vec![Payment {
|
vec![Payment {
|
||||||
address: N::external_address(&network, key).await,
|
address: N::external_address(&network, key).await,
|
||||||
balance: Balance {
|
balance: ExternalBalance {
|
||||||
coin: match N::NETWORK {
|
coin: match N::NETWORK {
|
||||||
NetworkId::Serai => panic!("test_wallet called with Serai"),
|
ExternalNetworkId::Bitcoin => ExternalCoin::Bitcoin,
|
||||||
NetworkId::Bitcoin => Coin::Bitcoin,
|
ExternalNetworkId::Ethereum => ExternalCoin::Ether,
|
||||||
NetworkId::Ethereum => Coin::Ether,
|
ExternalNetworkId::Monero => ExternalCoin::Monero,
|
||||||
NetworkId::Monero => Coin::Monero,
|
|
||||||
},
|
},
|
||||||
amount: Amount(amount),
|
amount: Amount(amount),
|
||||||
}
|
}
|
||||||
@@ -160,7 +158,7 @@ pub async fn test_wallet<N: Network>(
|
|||||||
|
|
||||||
// Don't run if Ethereum as the received output will revert by the contract
|
// Don't run if Ethereum as the received output will revert by the contract
|
||||||
// (and therefore not actually exist)
|
// (and therefore not actually exist)
|
||||||
if N::NETWORK != NetworkId::Ethereum {
|
if N::NETWORK != ExternalNetworkId::Ethereum {
|
||||||
assert_eq!(outputs.len(), 1 + usize::from(u8::from(plans[0].change.is_some())));
|
assert_eq!(outputs.len(), 1 + usize::from(u8::from(plans[0].change.is_some())));
|
||||||
// Adjust the amount for the fees
|
// Adjust the amount for the fees
|
||||||
let amount = amount - tx.fee(&network).await;
|
let amount = amount - tx.fee(&network).await;
|
||||||
@@ -183,7 +181,7 @@ pub async fn test_wallet<N: Network>(
|
|||||||
network.mine_block().await;
|
network.mine_block().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if N::NETWORK != NetworkId::Ethereum {
|
if N::NETWORK != ExternalNetworkId::Ethereum {
|
||||||
match timeout(Duration::from_secs(30), scanner.events.recv()).await.unwrap().unwrap() {
|
match timeout(Duration::from_secs(30), scanner.events.recv()).await.unwrap().unwrap() {
|
||||||
ScannerEvent::Block { is_retirement_block, block: block_id, outputs: these_outputs } => {
|
ScannerEvent::Block { is_retirement_block, block: block_id, outputs: these_outputs } => {
|
||||||
scanner.multisig_completed.send(false).unwrap();
|
scanner.multisig_completed.send(false).unwrap();
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ instructed to act on invalid data, it will drop the entire instruction.
|
|||||||
|
|
||||||
### Serialization
|
### Serialization
|
||||||
|
|
||||||
Instructions are SCALE encoded.
|
Instructions are [SCALE](https://docs.substrate.io/reference/scale-codec/) encoded.
|
||||||
|
|
||||||
### In Instruction
|
### In Instruction
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use sp_runtime::BoundedVec;
|
|||||||
|
|
||||||
use serai_primitives::*;
|
use serai_primitives::*;
|
||||||
|
|
||||||
type PoolId = Coin;
|
type PoolId = ExternalCoin;
|
||||||
type MaxSwapPathLength = sp_core::ConstU32<3>;
|
type MaxSwapPathLength = sp_core::ConstU32<3>;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||||
@@ -10,7 +10,7 @@ type MaxSwapPathLength = sp_core::ConstU32<3>;
|
|||||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||||
pub enum Call {
|
pub enum Call {
|
||||||
add_liquidity {
|
add_liquidity {
|
||||||
coin: Coin,
|
coin: ExternalCoin,
|
||||||
coin_desired: SubstrateAmount,
|
coin_desired: SubstrateAmount,
|
||||||
sri_desired: SubstrateAmount,
|
sri_desired: SubstrateAmount,
|
||||||
coin_min: SubstrateAmount,
|
coin_min: SubstrateAmount,
|
||||||
@@ -18,7 +18,7 @@ pub enum Call {
|
|||||||
mint_to: SeraiAddress,
|
mint_to: SeraiAddress,
|
||||||
},
|
},
|
||||||
remove_liquidity {
|
remove_liquidity {
|
||||||
coin: Coin,
|
coin: ExternalCoin,
|
||||||
lp_token_burn: SubstrateAmount,
|
lp_token_burn: SubstrateAmount,
|
||||||
coin_min_receive: SubstrateAmount,
|
coin_min_receive: SubstrateAmount,
|
||||||
sri_min_receive: SubstrateAmount,
|
sri_min_receive: SubstrateAmount,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use serai_primitives::NetworkId;
|
use serai_primitives::ExternalNetworkId;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
EconomicSecurityReached { network: NetworkId },
|
EconomicSecurityReached { network: ExternalNetworkId },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use primitives::*;
|
|||||||
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum Call {
|
pub enum Call {
|
||||||
remove_coin_liquidity { balance: Balance },
|
remove_coin_liquidity { balance: ExternalBalance },
|
||||||
oraclize_values { values: Values, signature: Signature },
|
oraclize_values { values: Values, signature: Signature },
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,8 +14,7 @@ pub enum Call {
|
|||||||
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
GenesisLiquidityAdded { by: SeraiAddress, balance: Balance },
|
GenesisLiquidityAdded { by: SeraiAddress, balance: ExternalBalance },
|
||||||
GenesisLiquidityRemoved { by: SeraiAddress, balance: Balance },
|
GenesisLiquidityRemoved { by: SeraiAddress, balance: ExternalBalance },
|
||||||
GenesisLiquidityAddedToPool { coin1: Balance, coin2: Balance },
|
GenesisLiquidityAddedToPool { coin: ExternalBalance, sri: Amount },
|
||||||
EconomicSecurityReached { network: NetworkId },
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub enum Call {
|
|||||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Batch {
|
Batch {
|
||||||
network: NetworkId,
|
network: ExternalNetworkId,
|
||||||
publishing_session: Session,
|
publishing_session: Session,
|
||||||
id: u32,
|
id: u32,
|
||||||
external_network_block_hash: BlockHash,
|
external_network_block_hash: BlockHash,
|
||||||
@@ -25,6 +25,6 @@ pub enum Event {
|
|||||||
in_instruction_results: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>,
|
in_instruction_results: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>,
|
||||||
},
|
},
|
||||||
Halt {
|
Halt {
|
||||||
network: NetworkId,
|
network: ExternalNetworkId,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use serai_validator_sets_primitives::*;
|
|||||||
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))]
|
||||||
pub enum Call {
|
pub enum Call {
|
||||||
set_keys {
|
set_keys {
|
||||||
network: NetworkId,
|
network: ExternalNetworkId,
|
||||||
key_pair: KeyPair,
|
key_pair: KeyPair,
|
||||||
signature_participants: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>,
|
signature_participants: bitvec::vec::BitVec<u8, bitvec::order::Lsb0>,
|
||||||
signature: Signature,
|
signature: Signature,
|
||||||
@@ -20,7 +20,7 @@ pub enum Call {
|
|||||||
key: BoundedVec<u8, ConstU32<{ MAX_KEY_LEN }>>,
|
key: BoundedVec<u8, ConstU32<{ MAX_KEY_LEN }>>,
|
||||||
},
|
},
|
||||||
report_slashes {
|
report_slashes {
|
||||||
network: NetworkId,
|
network: ExternalNetworkId,
|
||||||
slashes: SlashReport,
|
slashes: SlashReport,
|
||||||
signature: Signature,
|
signature: Signature,
|
||||||
},
|
},
|
||||||
@@ -51,7 +51,7 @@ pub enum Event {
|
|||||||
removed: SeraiAddress,
|
removed: SeraiAddress,
|
||||||
},
|
},
|
||||||
KeyGen {
|
KeyGen {
|
||||||
set: ValidatorSet,
|
set: ExternalValidatorSet,
|
||||||
key_pair: KeyPair,
|
key_pair: KeyPair,
|
||||||
},
|
},
|
||||||
AcceptedHandover {
|
AcceptedHandover {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use sp_core::bounded_vec::BoundedVec;
|
use sp_core::bounded_vec::BoundedVec;
|
||||||
use serai_abi::primitives::{SeraiAddress, Amount, Coin};
|
use serai_abi::primitives::{Amount, Coin, ExternalCoin, SeraiAddress};
|
||||||
|
|
||||||
use crate::{SeraiError, TemporalSerai};
|
use crate::{SeraiError, TemporalSerai};
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ impl SeraiDex<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_liquidity(
|
pub fn add_liquidity(
|
||||||
coin: Coin,
|
coin: ExternalCoin,
|
||||||
coin_amount: Amount,
|
coin_amount: Amount,
|
||||||
sri_amount: Amount,
|
sri_amount: Amount,
|
||||||
min_coin_amount: Amount,
|
min_coin_amount: Amount,
|
||||||
@@ -61,11 +61,14 @@ impl SeraiDex<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the reserves of `coin:SRI` pool.
|
/// Returns the reserves of `coin:SRI` pool.
|
||||||
pub async fn get_reserves(&self, coin: Coin) -> Result<Option<(Amount, Amount)>, SeraiError> {
|
pub async fn get_reserves(
|
||||||
self.0.runtime_api("DexApi_get_reserves", (coin, Coin::Serai)).await
|
&self,
|
||||||
|
coin: ExternalCoin,
|
||||||
|
) -> Result<Option<(Amount, Amount)>, SeraiError> {
|
||||||
|
self.0.runtime_api("DexApi_get_reserves", (Coin::from(coin), Coin::Serai)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn oracle_value(&self, coin: Coin) -> Result<Option<Amount>, SeraiError> {
|
pub async fn oracle_value(&self, coin: ExternalCoin) -> Result<Option<Amount>, SeraiError> {
|
||||||
self.0.storage(PALLET, "SecurityOracleValue", coin).await
|
self.0.storage(PALLET, "SecurityOracleValue", coin).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ impl SeraiGenesisLiquidity<'_> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_coin_liquidity(balance: Balance) -> serai_abi::Call {
|
pub fn remove_coin_liquidity(balance: ExternalBalance) -> serai_abi::Call {
|
||||||
serai_abi::Call::GenesisLiquidity(serai_abi::genesis_liquidity::Call::remove_coin_liquidity {
|
serai_abi::Call::GenesisLiquidity(serai_abi::genesis_liquidity::Call::remove_coin_liquidity {
|
||||||
balance,
|
balance,
|
||||||
})
|
})
|
||||||
@@ -44,7 +44,7 @@ impl SeraiGenesisLiquidity<'_> {
|
|||||||
pub async fn liquidity(
|
pub async fn liquidity(
|
||||||
&self,
|
&self,
|
||||||
address: &SeraiAddress,
|
address: &SeraiAddress,
|
||||||
coin: Coin,
|
coin: ExternalCoin,
|
||||||
) -> Result<LiquidityAmount, SeraiError> {
|
) -> Result<LiquidityAmount, SeraiError> {
|
||||||
Ok(
|
Ok(
|
||||||
self
|
self
|
||||||
@@ -59,7 +59,7 @@ impl SeraiGenesisLiquidity<'_> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn supply(&self, coin: Coin) -> Result<LiquidityAmount, SeraiError> {
|
pub async fn supply(&self, coin: ExternalCoin) -> Result<LiquidityAmount, SeraiError> {
|
||||||
Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(LiquidityAmount::zero()))
|
Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(LiquidityAmount::zero()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
pub use serai_abi::in_instructions::primitives;
|
pub use serai_abi::in_instructions::primitives;
|
||||||
use primitives::SignedBatch;
|
use primitives::SignedBatch;
|
||||||
|
|
||||||
use crate::{primitives::NetworkId, Transaction, SeraiError, Serai, TemporalSerai};
|
use crate::{
|
||||||
|
primitives::{BlockHash, ExternalNetworkId},
|
||||||
|
Transaction, SeraiError, Serai, TemporalSerai,
|
||||||
|
};
|
||||||
|
|
||||||
pub type InInstructionsEvent = serai_abi::in_instructions::Event;
|
pub type InInstructionsEvent = serai_abi::in_instructions::Event;
|
||||||
|
|
||||||
@@ -9,10 +12,9 @@ const PALLET: &str = "InInstructions";
|
|||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct SeraiInInstructions<'a>(pub(crate) &'a TemporalSerai<'a>);
|
pub struct SeraiInInstructions<'a>(pub(crate) &'a TemporalSerai<'a>);
|
||||||
impl SeraiInInstructions<'_> {
|
|
||||||
pub async fn last_batch_for_network(
|
pub async fn last_batch_for_network(
|
||||||
&self,
|
&self,
|
||||||
network: NetworkId,
|
network: ExternalNetworkId,
|
||||||
) -> Result<Option<u32>, SeraiError> {
|
) -> Result<Option<u32>, SeraiError> {
|
||||||
self.0.storage(PALLET, "LastBatch", network).await
|
self.0.storage(PALLET, "LastBatch", network).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use scale::Encode;
|
use scale::Encode;
|
||||||
|
|
||||||
use serai_abi::primitives::{SeraiAddress, Amount, Coin, Balance};
|
use serai_abi::primitives::{Amount, ExternalBalance, ExternalCoin, SeraiAddress};
|
||||||
|
|
||||||
use crate::{TemporalSerai, SeraiError};
|
use crate::{TemporalSerai, SeraiError};
|
||||||
|
|
||||||
@@ -9,13 +9,13 @@ const PALLET: &str = "LiquidityTokens";
|
|||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct SeraiLiquidityTokens<'a>(pub(crate) &'a TemporalSerai<'a>);
|
pub struct SeraiLiquidityTokens<'a>(pub(crate) &'a TemporalSerai<'a>);
|
||||||
impl SeraiLiquidityTokens<'_> {
|
impl SeraiLiquidityTokens<'_> {
|
||||||
pub async fn token_supply(&self, coin: Coin) -> Result<Amount, SeraiError> {
|
pub async fn token_supply(&self, coin: ExternalCoin) -> Result<Amount, SeraiError> {
|
||||||
Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(Amount(0)))
|
Ok(self.0.storage(PALLET, "Supply", coin).await?.unwrap_or(Amount(0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn token_balance(
|
pub async fn token_balance(
|
||||||
&self,
|
&self,
|
||||||
coin: Coin,
|
coin: ExternalCoin,
|
||||||
address: SeraiAddress,
|
address: SeraiAddress,
|
||||||
) -> Result<Amount, SeraiError> {
|
) -> Result<Amount, SeraiError> {
|
||||||
Ok(
|
Ok(
|
||||||
@@ -31,11 +31,16 @@ impl SeraiLiquidityTokens<'_> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transfer(to: SeraiAddress, balance: Balance) -> serai_abi::Call {
|
pub fn transfer(to: SeraiAddress, balance: ExternalBalance) -> serai_abi::Call {
|
||||||
serai_abi::Call::LiquidityTokens(serai_abi::liquidity_tokens::Call::transfer { to, balance })
|
serai_abi::Call::LiquidityTokens(serai_abi::liquidity_tokens::Call::transfer {
|
||||||
|
to,
|
||||||
|
balance: balance.into(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn burn(balance: Balance) -> serai_abi::Call {
|
pub fn burn(balance: ExternalBalance) -> serai_abi::Call {
|
||||||
serai_abi::Call::LiquidityTokens(serai_abi::liquidity_tokens::Call::burn { balance })
|
serai_abi::Call::LiquidityTokens(serai_abi::liquidity_tokens::Call::burn {
|
||||||
|
balance: balance.into(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ use scale::Encode;
|
|||||||
use sp_core::sr25519::{Public, Signature};
|
use sp_core::sr25519::{Public, Signature};
|
||||||
use sp_runtime::BoundedVec;
|
use sp_runtime::BoundedVec;
|
||||||
|
|
||||||
use serai_abi::primitives::Amount;
|
use serai_abi::{primitives::Amount, validator_sets::primitives::ExternalValidatorSet};
|
||||||
pub use serai_abi::validator_sets::primitives;
|
pub use serai_abi::validator_sets::primitives;
|
||||||
use primitives::{MAX_KEY_LEN, Session, ValidatorSet, KeyPair, SlashReport};
|
use primitives::{MAX_KEY_LEN, Session, ValidatorSet, KeyPair, SlashReport};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
primitives::{EmbeddedEllipticCurve, NetworkId},
|
primitives::{NetworkId, ExternalNetworkId, EmbeddedEllipticCurve, SeraiAddress},
|
||||||
Transaction, Serai, TemporalSerai, SeraiError,
|
Transaction, Serai, TemporalSerai, SeraiError,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -183,13 +183,13 @@ impl SeraiValidatorSets<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Store these separately since we almost never need both at once?
|
// TODO: Store these separately since we almost never need both at once?
|
||||||
pub async fn keys(&self, set: ValidatorSet) -> Result<Option<KeyPair>, SeraiError> {
|
pub async fn keys(&self, set: ExternalValidatorSet) -> Result<Option<KeyPair>, SeraiError> {
|
||||||
self.0.storage(PALLET, "Keys", (sp_core::hashing::twox_64(&set.encode()), set)).await
|
self.0.storage(PALLET, "Keys", (sp_core::hashing::twox_64(&set.encode()), set)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn key_pending_slash_report(
|
pub async fn key_pending_slash_report(
|
||||||
&self,
|
&self,
|
||||||
network: NetworkId,
|
network: ExternalNetworkId,
|
||||||
) -> Result<Option<Public>, SeraiError> {
|
) -> Result<Option<Public>, SeraiError> {
|
||||||
self.0.storage(PALLET, "PendingSlashReport", network).await
|
self.0.storage(PALLET, "PendingSlashReport", network).await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use blake2::{
|
|||||||
use scale::Encode;
|
use scale::Encode;
|
||||||
|
|
||||||
use serai_client::{
|
use serai_client::{
|
||||||
primitives::{BlockHash, NetworkId, Coin, Amount, Balance, SeraiAddress},
|
primitives::{BlockHash, NetworkId, ExternalCoin, Amount, ExternalBalance, SeraiAddress},
|
||||||
coins::CoinsEvent,
|
coins::CoinsEvent,
|
||||||
validator_sets::primitives::Session,
|
validator_sets::primitives::Session,
|
||||||
in_instructions::{
|
in_instructions::{
|
||||||
@@ -23,15 +23,15 @@ use common::in_instructions::provide_batch;
|
|||||||
|
|
||||||
serai_test!(
|
serai_test!(
|
||||||
publish_batch: (|serai: Serai| async move {
|
publish_batch: (|serai: Serai| async move {
|
||||||
let network = NetworkId::Bitcoin;
|
|
||||||
let id = 0;
|
let id = 0;
|
||||||
|
|
||||||
let mut address = SeraiAddress::new([0; 32]);
|
let mut address = SeraiAddress::new([0; 32]);
|
||||||
OsRng.fill_bytes(&mut address.0);
|
OsRng.fill_bytes(&mut address.0);
|
||||||
|
|
||||||
let coin = Coin::Bitcoin;
|
let coin = ExternalCoin::Bitcoin;
|
||||||
|
let network = coin.network();
|
||||||
let amount = Amount(OsRng.next_u64().saturating_add(1));
|
let amount = Amount(OsRng.next_u64().saturating_add(1));
|
||||||
let balance = Balance { coin, amount };
|
let balance = ExternalBalance { coin, amount };
|
||||||
|
|
||||||
let mut external_network_block_hash = BlockHash([0; 32]);
|
let mut external_network_block_hash = BlockHash([0; 32]);
|
||||||
OsRng.fill_bytes(&mut external_network_block_hash.0);
|
OsRng.fill_bytes(&mut external_network_block_hash.0);
|
||||||
@@ -68,9 +68,9 @@ serai_test!(
|
|||||||
let serai = serai.coins();
|
let serai = serai.coins();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serai.mint_events().await.unwrap(),
|
serai.mint_events().await.unwrap(),
|
||||||
vec![CoinsEvent::Mint { to: address, balance }]
|
vec![CoinsEvent::Mint { to: address, balance: balance.into() }]
|
||||||
);
|
);
|
||||||
assert_eq!(serai.coin_supply(coin).await.unwrap(), amount);
|
assert_eq!(serai.coin_supply(coin.into()).await.unwrap(), amount);
|
||||||
assert_eq!(serai.coin_balance(coin, address).await.unwrap(), amount);
|
assert_eq!(serai.coin_balance(coin.into(), address).await.unwrap(), amount);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use sp_core::Pair;
|
|||||||
|
|
||||||
use serai_client::{
|
use serai_client::{
|
||||||
primitives::{
|
primitives::{
|
||||||
BlockHash, NetworkId, Coin, Amount, Balance, SeraiAddress, ExternalAddress,
|
BlockHash, ExternalNetworkId, ExternalCoin, Amount, ExternalBalance, SeraiAddress, ExternalAddress,
|
||||||
insecure_pair_from_name,
|
insecure_pair_from_name,
|
||||||
},
|
},
|
||||||
coins::{
|
coins::{
|
||||||
@@ -31,9 +31,7 @@ use common::{tx::publish_tx, in_instructions::provide_batch};
|
|||||||
|
|
||||||
serai_test!(
|
serai_test!(
|
||||||
burn: (|serai: Serai| async move {
|
burn: (|serai: Serai| async move {
|
||||||
let network = NetworkId::Bitcoin;
|
|
||||||
let id = 0;
|
let id = 0;
|
||||||
|
|
||||||
let mut block_hash = BlockHash([0; 32]);
|
let mut block_hash = BlockHash([0; 32]);
|
||||||
OsRng.fill_bytes(&mut block_hash.0);
|
OsRng.fill_bytes(&mut block_hash.0);
|
||||||
|
|
||||||
@@ -41,9 +39,10 @@ serai_test!(
|
|||||||
let public = pair.public();
|
let public = pair.public();
|
||||||
let address = SeraiAddress::from(public);
|
let address = SeraiAddress::from(public);
|
||||||
|
|
||||||
let coin = Coin::Bitcoin;
|
let coin = ExternalCoin::Bitcoin;
|
||||||
|
let network = coin.network();
|
||||||
let amount = Amount(OsRng.next_u64().saturating_add(1));
|
let amount = Amount(OsRng.next_u64().saturating_add(1));
|
||||||
let balance = Balance { coin, amount };
|
let balance = ExternalBalance { coin, amount };
|
||||||
|
|
||||||
let batch = Batch {
|
let batch = Batch {
|
||||||
network,
|
network,
|
||||||
@@ -72,12 +71,12 @@ serai_test!(
|
|||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serai.coins().mint_events().await.unwrap(),
|
serai.coins().mint_events().await.unwrap(),
|
||||||
vec![CoinsEvent::Mint { to: address, balance }]
|
vec![CoinsEvent::Mint { to: address, balance: balance.into() }]
|
||||||
);
|
);
|
||||||
assert_eq!(serai.coins().coin_supply(coin).await.unwrap(), amount);
|
assert_eq!(serai.coins().coin_supply(coin.into()).await.unwrap(), amount);
|
||||||
assert_eq!(serai.coins().coin_balance(coin, address).await.unwrap(), amount);
|
assert_eq!(serai.coins().coin_balance(coin.into(), address).await.unwrap(), amount);
|
||||||
|
|
||||||
// Now burn it
|
// Now burn it
|
||||||
let mut rand_bytes = vec![0; 32];
|
let mut rand_bytes = vec![0; 32];
|
||||||
@@ -100,7 +99,7 @@ serai_test!(
|
|||||||
let serai = serai.coins();
|
let serai = serai.coins();
|
||||||
let events = serai.burn_with_instruction_events().await.unwrap();
|
let events = serai.burn_with_instruction_events().await.unwrap();
|
||||||
assert_eq!(events, vec![CoinsEvent::BurnWithInstruction { from: address, instruction }]);
|
assert_eq!(events, vec![CoinsEvent::BurnWithInstruction { from: address, instruction }]);
|
||||||
assert_eq!(serai.coin_supply(coin).await.unwrap(), Amount(0));
|
assert_eq!(serai.coin_supply(coin.into()).await.unwrap(), Amount(0));
|
||||||
assert_eq!(serai.coin_balance(coin, address).await.unwrap(), Amount(0));
|
assert_eq!(serai.coin_balance(coin.into(), address).await.unwrap(), Amount(0));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use serai_abi::primitives::{Coin, Amount};
|
use serai_abi::primitives::{Amount, Coin, ExternalCoin};
|
||||||
|
|
||||||
use serai_client::{Serai, SeraiDex};
|
use serai_client::{Serai, SeraiDex};
|
||||||
use sp_core::{sr25519::Pair, Pair as PairTrait};
|
use sp_core::{sr25519::Pair, Pair as PairTrait};
|
||||||
@@ -8,7 +8,7 @@ use crate::common::tx::publish_tx;
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn add_liquidity(
|
pub async fn add_liquidity(
|
||||||
serai: &Serai,
|
serai: &Serai,
|
||||||
coin: Coin,
|
coin: ExternalCoin,
|
||||||
coin_amount: Amount,
|
coin_amount: Amount,
|
||||||
sri_amount: Amount,
|
sri_amount: Amount,
|
||||||
nonce: u32,
|
nonce: u32,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use sp_core::{sr25519::Signature, Pair as PairTrait};
|
|||||||
|
|
||||||
use serai_abi::{
|
use serai_abi::{
|
||||||
primitives::{
|
primitives::{
|
||||||
BlockHash, NetworkId, Coin, Amount, Balance, SeraiAddress, insecure_pair_from_name,
|
BlockHash, ExternalNetworkId, ExternalCoin, Amount, ExternalBalance, SeraiAddress, insecure_pair_from_name,
|
||||||
},
|
},
|
||||||
validator_sets::primitives::{musig_context, Session, ValidatorSet},
|
validator_sets::primitives::{musig_context, Session, ValidatorSet},
|
||||||
genesis_liquidity::primitives::{oraclize_values_message, Values},
|
genesis_liquidity::primitives::{oraclize_values_message, Values},
|
||||||
@@ -25,12 +25,11 @@ use crate::common::{in_instructions::provide_batch, tx::publish_tx};
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn set_up_genesis(
|
pub async fn set_up_genesis(
|
||||||
serai: &Serai,
|
serai: &Serai,
|
||||||
coins: &[Coin],
|
values: &HashMap<ExternalCoin, u64>,
|
||||||
values: &HashMap<Coin, u64>,
|
) -> (HashMap<ExternalCoin, Vec<(SeraiAddress, Amount)>>, HashMap<ExternalNetworkId, u32>) {
|
||||||
) -> (HashMap<Coin, Vec<(SeraiAddress, Amount)>>, HashMap<NetworkId, u32>) {
|
|
||||||
// make accounts with amounts
|
// make accounts with amounts
|
||||||
let mut accounts = HashMap::new();
|
let mut accounts = HashMap::new();
|
||||||
for coin in coins {
|
for coin in EXTERNAL_COINS {
|
||||||
// make 5 accounts per coin
|
// make 5 accounts per coin
|
||||||
let mut values = vec![];
|
let mut values = vec![];
|
||||||
for _ in 0 .. 5 {
|
for _ in 0 .. 5 {
|
||||||
@@ -38,18 +37,18 @@ pub async fn set_up_genesis(
|
|||||||
OsRng.fill_bytes(&mut address.0);
|
OsRng.fill_bytes(&mut address.0);
|
||||||
values.push((address, Amount(OsRng.next_u64() % 10u64.pow(coin.decimals()))));
|
values.push((address, Amount(OsRng.next_u64() % 10u64.pow(coin.decimals()))));
|
||||||
}
|
}
|
||||||
accounts.insert(*coin, values);
|
accounts.insert(coin, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
// send a batch per coin
|
// send a batch per coin
|
||||||
let mut batch_ids: HashMap<NetworkId, u32> = HashMap::new();
|
let mut batch_ids: HashMap<ExternalNetworkId, u32> = HashMap::new();
|
||||||
for coin in coins {
|
for coin in EXTERNAL_COINS {
|
||||||
// set up instructions
|
// set up instructions
|
||||||
let instructions = accounts[coin]
|
let instructions = accounts[&coin]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(addr, amount)| InInstructionWithBalance {
|
.map(|(addr, amount)| InInstructionWithBalance {
|
||||||
instruction: InInstruction::GenesisLiquidity(*addr),
|
instruction: InInstruction::GenesisLiquidity(*addr),
|
||||||
balance: Balance { coin: *coin, amount: *amount },
|
balance: ExternalBalance { coin, amount: *amount },
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
@@ -77,8 +76,11 @@ pub async fn set_up_genesis(
|
|||||||
// set values relative to each other. We can do that without checking for genesis period blocks
|
// set values relative to each other. We can do that without checking for genesis period blocks
|
||||||
// since we are running in test(fast-epoch) mode.
|
// since we are running in test(fast-epoch) mode.
|
||||||
// TODO: Random values here
|
// TODO: Random values here
|
||||||
let values =
|
let values = Values {
|
||||||
Values { monero: values[&Coin::Monero], ether: values[&Coin::Ether], dai: values[&Coin::Dai] };
|
monero: values[&ExternalCoin::Monero],
|
||||||
|
ether: values[&ExternalCoin::Ether],
|
||||||
|
dai: values[&ExternalCoin::Dai],
|
||||||
|
};
|
||||||
set_values(serai, &values).await;
|
set_values(serai, &values).await;
|
||||||
|
|
||||||
(accounts, batch_ids)
|
(accounts, batch_ids)
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ use scale::Encode;
|
|||||||
use sp_core::Pair;
|
use sp_core::Pair;
|
||||||
|
|
||||||
use serai_client::{
|
use serai_client::{
|
||||||
primitives::{BlockHash, NetworkId, Balance, SeraiAddress, insecure_pair_from_name},
|
primitives::{BlockHash, NetworkId, ExternalBalance, SeraiAddress, insecure_pair_from_name},
|
||||||
validator_sets::primitives::{ValidatorSet, KeyPair},
|
validator_sets::primitives::{ExternalValidatorSet, KeyPair},
|
||||||
in_instructions::{
|
in_instructions::{
|
||||||
primitives::{Batch, SignedBatch, batch_message, InInstruction, InInstructionWithBalance},
|
primitives::{Batch, SignedBatch, batch_message, InInstruction, InInstructionWithBalance},
|
||||||
InInstructionsEvent,
|
InInstructionsEvent,
|
||||||
@@ -23,8 +23,8 @@ use crate::common::{tx::publish_tx, validator_sets::set_keys};
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn provide_batch(serai: &Serai, batch: Batch) -> [u8; 32] {
|
pub async fn provide_batch(serai: &Serai, batch: Batch) -> [u8; 32] {
|
||||||
let serai_latest = serai.as_of_latest_finalized_block().await.unwrap();
|
let serai_latest = serai.as_of_latest_finalized_block().await.unwrap();
|
||||||
let session = serai_latest.validator_sets().session(batch.network).await.unwrap().unwrap();
|
let session = serai_latest.validator_sets().session(batch.network.into()).await.unwrap().unwrap();
|
||||||
let set = ValidatorSet { session, network: batch.network };
|
let set = ExternalValidatorSet { session, network: batch.network };
|
||||||
|
|
||||||
let pair = insecure_pair_from_name(&format!("ValidatorSet {set:?}"));
|
let pair = insecure_pair_from_name(&format!("ValidatorSet {set:?}"));
|
||||||
let keys = if let Some(keys) = serai_latest.validator_sets().keys(set).await.unwrap() {
|
let keys = if let Some(keys) = serai_latest.validator_sets().keys(set).await.unwrap() {
|
||||||
@@ -77,8 +77,7 @@ pub async fn provide_batch(serai: &Serai, batch: Batch) -> [u8; 32] {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn mint_coin(
|
pub async fn mint_coin(
|
||||||
serai: &Serai,
|
serai: &Serai,
|
||||||
balance: Balance,
|
balance: ExternalBalance,
|
||||||
network: NetworkId,
|
|
||||||
batch_id: u32,
|
batch_id: u32,
|
||||||
address: SeraiAddress,
|
address: SeraiAddress,
|
||||||
) -> [u8; 32] {
|
) -> [u8; 32] {
|
||||||
@@ -86,7 +85,7 @@ pub async fn mint_coin(
|
|||||||
OsRng.fill_bytes(&mut block_hash.0);
|
OsRng.fill_bytes(&mut block_hash.0);
|
||||||
|
|
||||||
let batch = Batch {
|
let batch = Batch {
|
||||||
network,
|
network: balance.coin.network(),
|
||||||
id: batch_id,
|
id: batch_id,
|
||||||
external_network_block_hash: block_hash,
|
external_network_block_hash: block_hash,
|
||||||
instructions: vec![InInstructionWithBalance {
|
instructions: vec![InInstructionWithBalance {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use schnorrkel::Schnorrkel;
|
|||||||
use serai_client::{
|
use serai_client::{
|
||||||
primitives::EmbeddedEllipticCurve,
|
primitives::EmbeddedEllipticCurve,
|
||||||
validator_sets::{
|
validator_sets::{
|
||||||
primitives::{MAX_KEY_LEN, ValidatorSet, KeyPair, musig_context, set_keys_message},
|
primitives::{MAX_KEY_LEN, ExternalValidatorSet, KeyPair, musig_context, set_keys_message},
|
||||||
ValidatorSetsEvent,
|
ValidatorSetsEvent,
|
||||||
},
|
},
|
||||||
Amount, Serai, SeraiValidatorSets,
|
Amount, Serai, SeraiValidatorSets,
|
||||||
@@ -29,7 +29,7 @@ use crate::common::tx::publish_tx;
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn set_keys(
|
pub async fn set_keys(
|
||||||
serai: &Serai,
|
serai: &Serai,
|
||||||
set: ValidatorSet,
|
set: ExternalValidatorSet,
|
||||||
key_pair: KeyPair,
|
key_pair: KeyPair,
|
||||||
pairs: &[Pair],
|
pairs: &[Pair],
|
||||||
) -> [u8; 32] {
|
) -> [u8; 32] {
|
||||||
@@ -49,7 +49,8 @@ pub async fn set_keys(
|
|||||||
assert_eq!(Ristretto::generator() * secret_key, pub_keys[i]);
|
assert_eq!(Ristretto::generator() * secret_key, pub_keys[i]);
|
||||||
|
|
||||||
threshold_keys.push(
|
threshold_keys.push(
|
||||||
musig::<Ristretto>(&musig_context(set), &Zeroizing::new(secret_key), &pub_keys).unwrap(),
|
musig::<Ristretto>(&musig_context(set.into()), &Zeroizing::new(secret_key), &pub_keys)
|
||||||
|
.unwrap(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,15 +28,14 @@ use common::{
|
|||||||
// TODO: Check Transfer events
|
// TODO: Check Transfer events
|
||||||
serai_test!(
|
serai_test!(
|
||||||
add_liquidity: (|serai: Serai| async move {
|
add_liquidity: (|serai: Serai| async move {
|
||||||
let coin = Coin::Monero;
|
let coin = ExternalCoin::Monero;
|
||||||
let pair = insecure_pair_from_name("Ferdie");
|
let pair = insecure_pair_from_name("Ferdie");
|
||||||
|
|
||||||
// mint sriXMR in the account so that we can add liq.
|
// mint sriXMR in the account so that we can add liq.
|
||||||
// Ferdie account is already pre-funded with SRI.
|
// Ferdie account is already pre-funded with SRI.
|
||||||
mint_coin(
|
mint_coin(
|
||||||
&serai,
|
&serai,
|
||||||
Balance { coin, amount: Amount(100_000_000_000_000) },
|
ExternalBalance { coin, amount: Amount(100_000_000_000_000) },
|
||||||
NetworkId::Monero,
|
|
||||||
0,
|
0,
|
||||||
pair.clone().public().into(),
|
pair.clone().public().into(),
|
||||||
)
|
)
|
||||||
@@ -61,7 +60,7 @@ serai_test!(
|
|||||||
vec![DexEvent::LiquidityAdded {
|
vec![DexEvent::LiquidityAdded {
|
||||||
who: pair.public().into(),
|
who: pair.public().into(),
|
||||||
mint_to: pair.public().into(),
|
mint_to: pair.public().into(),
|
||||||
pool_id: Coin::Monero,
|
pool_id: coin,
|
||||||
coin_amount: coin_amount.0,
|
coin_amount: coin_amount.0,
|
||||||
sri_amount: sri_amount.0,
|
sri_amount: sri_amount.0,
|
||||||
lp_token_minted: 49_999999990000
|
lp_token_minted: 49_999999990000
|
||||||
@@ -71,15 +70,14 @@ serai_test!(
|
|||||||
|
|
||||||
// Tests coin -> SRI and SRI -> coin swaps.
|
// Tests coin -> SRI and SRI -> coin swaps.
|
||||||
swap_coin_to_sri: (|serai: Serai| async move {
|
swap_coin_to_sri: (|serai: Serai| async move {
|
||||||
let coin = Coin::Ether;
|
let coin = ExternalCoin::Ether;
|
||||||
let pair = insecure_pair_from_name("Ferdie");
|
let pair = insecure_pair_from_name("Ferdie");
|
||||||
|
|
||||||
// mint sriXMR in the account so that we can add liq.
|
// mint sriXMR in the account so that we can add liq.
|
||||||
// Ferdie account is already pre-funded with SRI.
|
// Ferdie account is already pre-funded with SRI.
|
||||||
mint_coin(
|
mint_coin(
|
||||||
&serai,
|
&serai,
|
||||||
Balance { coin, amount: Amount(100_000_000_000_000) },
|
ExternalBalance { coin, amount: Amount(100_000_000_000_000) },
|
||||||
NetworkId::Ethereum,
|
|
||||||
0,
|
0,
|
||||||
pair.clone().public().into(),
|
pair.clone().public().into(),
|
||||||
)
|
)
|
||||||
@@ -96,14 +94,21 @@ serai_test!(
|
|||||||
|
|
||||||
// now that we have our liquid pool, swap some coin to SRI.
|
// now that we have our liquid pool, swap some coin to SRI.
|
||||||
let mut amount_in = Amount(25_000_000_000_000);
|
let mut amount_in = Amount(25_000_000_000_000);
|
||||||
let mut block = common_swap(&serai, coin, Coin::Serai, amount_in, Amount(1), 1, pair.clone())
|
let mut block = common_swap(
|
||||||
|
&serai,
|
||||||
|
coin.into(),
|
||||||
|
Coin::Serai,
|
||||||
|
amount_in,
|
||||||
|
Amount(1),
|
||||||
|
1,
|
||||||
|
pair.clone())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// get only the swap events
|
// get only the swap events
|
||||||
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
||||||
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||||
|
|
||||||
let mut path = BoundedVec::try_from(vec![coin, Coin::Serai]).unwrap();
|
let mut path = BoundedVec::try_from(vec![coin.into(), Coin::Serai]).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events,
|
events,
|
||||||
vec![DexEvent::SwapExecuted {
|
vec![DexEvent::SwapExecuted {
|
||||||
@@ -117,13 +122,21 @@ serai_test!(
|
|||||||
|
|
||||||
// now swap some SRI to coin
|
// now swap some SRI to coin
|
||||||
amount_in = Amount(10_000_000_000_000);
|
amount_in = Amount(10_000_000_000_000);
|
||||||
block = common_swap(&serai, Coin::Serai, coin, amount_in, Amount(1), 2, pair.clone()).await;
|
block = common_swap(
|
||||||
|
&serai,
|
||||||
|
Coin::Serai,
|
||||||
|
coin.into(),
|
||||||
|
amount_in,
|
||||||
|
Amount(1),
|
||||||
|
2,
|
||||||
|
pair.clone()
|
||||||
|
).await;
|
||||||
|
|
||||||
// get only the swap events
|
// get only the swap events
|
||||||
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
||||||
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||||
|
|
||||||
path = BoundedVec::try_from(vec![Coin::Serai, coin]).unwrap();
|
path = BoundedVec::try_from(vec![Coin::Serai, coin.into()]).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events,
|
events,
|
||||||
vec![DexEvent::SwapExecuted {
|
vec![DexEvent::SwapExecuted {
|
||||||
@@ -137,23 +150,21 @@ serai_test!(
|
|||||||
})
|
})
|
||||||
|
|
||||||
swap_coin_to_coin: (|serai: Serai| async move {
|
swap_coin_to_coin: (|serai: Serai| async move {
|
||||||
let coin1 = Coin::Monero;
|
let coin1 = ExternalCoin::Monero;
|
||||||
let coin2 = Coin::Dai;
|
let coin2 = ExternalCoin::Dai;
|
||||||
let pair = insecure_pair_from_name("Ferdie");
|
let pair = insecure_pair_from_name("Ferdie");
|
||||||
|
|
||||||
// mint coins
|
// mint coins
|
||||||
mint_coin(
|
mint_coin(
|
||||||
&serai,
|
&serai,
|
||||||
Balance { coin: coin1, amount: Amount(100_000_000_000_000) },
|
ExternalBalance { coin: coin1, amount: Amount(100_000_000_000_000) },
|
||||||
NetworkId::Monero,
|
|
||||||
0,
|
0,
|
||||||
pair.clone().public().into(),
|
pair.clone().public().into(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
mint_coin(
|
mint_coin(
|
||||||
&serai,
|
&serai,
|
||||||
Balance { coin: coin2, amount: Amount(100_000_000_000_000) },
|
ExternalBalance { coin: coin2, amount: Amount(100_000_000_000_000) },
|
||||||
NetworkId::Ethereum,
|
|
||||||
0,
|
0,
|
||||||
pair.clone().public().into(),
|
pair.clone().public().into(),
|
||||||
)
|
)
|
||||||
@@ -177,13 +188,21 @@ serai_test!(
|
|||||||
|
|
||||||
// swap coin1 -> coin2
|
// swap coin1 -> coin2
|
||||||
let amount_in = Amount(25_000_000_000_000);
|
let amount_in = Amount(25_000_000_000_000);
|
||||||
let block = common_swap(&serai, coin1, coin2, amount_in, Amount(1), 2, pair.clone()).await;
|
let block = common_swap(
|
||||||
|
&serai,
|
||||||
|
coin1.into(),
|
||||||
|
coin2.into(),
|
||||||
|
amount_in,
|
||||||
|
Amount(1),
|
||||||
|
2,
|
||||||
|
pair.clone()
|
||||||
|
).await;
|
||||||
|
|
||||||
// get only the swap events
|
// get only the swap events
|
||||||
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
||||||
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||||
|
|
||||||
let path = BoundedVec::try_from(vec![coin1, Coin::Serai, coin2]).unwrap();
|
let path = BoundedVec::try_from(vec![coin1.into(), Coin::Serai, coin2.into()]).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events,
|
events,
|
||||||
vec![DexEvent::SwapExecuted {
|
vec![DexEvent::SwapExecuted {
|
||||||
@@ -197,7 +216,7 @@ serai_test!(
|
|||||||
})
|
})
|
||||||
|
|
||||||
add_liquidity_in_instructions: (|serai: Serai| async move {
|
add_liquidity_in_instructions: (|serai: Serai| async move {
|
||||||
let coin = Coin::Bitcoin;
|
let coin = ExternalCoin::Bitcoin;
|
||||||
let pair = insecure_pair_from_name("Ferdie");
|
let pair = insecure_pair_from_name("Ferdie");
|
||||||
let mut batch_id = 0;
|
let mut batch_id = 0;
|
||||||
|
|
||||||
@@ -205,8 +224,7 @@ serai_test!(
|
|||||||
// Ferdie account is already pre-funded with SRI.
|
// Ferdie account is already pre-funded with SRI.
|
||||||
mint_coin(
|
mint_coin(
|
||||||
&serai,
|
&serai,
|
||||||
Balance { coin, amount: Amount(100_000_000_000_000) },
|
ExternalBalance { coin, amount: Amount(100_000_000_000_000) },
|
||||||
NetworkId::Bitcoin,
|
|
||||||
batch_id,
|
batch_id,
|
||||||
pair.clone().public().into(),
|
pair.clone().public().into(),
|
||||||
)
|
)
|
||||||
@@ -227,12 +245,12 @@ serai_test!(
|
|||||||
let mut block_hash = BlockHash([0; 32]);
|
let mut block_hash = BlockHash([0; 32]);
|
||||||
OsRng.fill_bytes(&mut block_hash.0);
|
OsRng.fill_bytes(&mut block_hash.0);
|
||||||
let batch = Batch {
|
let batch = Batch {
|
||||||
network: NetworkId::Bitcoin,
|
network: coin.network(),
|
||||||
id: batch_id,
|
id: batch_id,
|
||||||
external_network_block_hash: block_hash,
|
external_network_block_hash: block_hash,
|
||||||
instructions: vec![InInstructionWithBalance {
|
instructions: vec![InInstructionWithBalance {
|
||||||
instruction: InInstruction::Dex(DexCall::SwapAndAddLiquidity(pair.public().into())),
|
instruction: InInstruction::Dex(DexCall::SwapAndAddLiquidity(pair.public().into())),
|
||||||
balance: Balance { coin: Coin::Bitcoin, amount: Amount(20_000_000_000_000) },
|
balance: ExternalBalance { coin, amount: Amount(20_000_000_000_000) },
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -244,7 +262,7 @@ serai_test!(
|
|||||||
vec![DexEvent::LiquidityAdded {
|
vec![DexEvent::LiquidityAdded {
|
||||||
who: IN_INSTRUCTION_EXECUTOR,
|
who: IN_INSTRUCTION_EXECUTOR,
|
||||||
mint_to: pair.public().into(),
|
mint_to: pair.public().into(),
|
||||||
pool_id: Coin::Bitcoin,
|
pool_id: coin,
|
||||||
coin_amount: 10_000_000_000_000, // half of sent amount
|
coin_amount: 10_000_000_000_000, // half of sent amount
|
||||||
sri_amount: 111_333_778_668,
|
sri_amount: 111_333_778_668,
|
||||||
lp_token_minted: 1_054_092_553_383
|
lp_token_minted: 1_054_092_553_383
|
||||||
@@ -253,8 +271,8 @@ serai_test!(
|
|||||||
})
|
})
|
||||||
|
|
||||||
swap_in_instructions: (|serai: Serai| async move {
|
swap_in_instructions: (|serai: Serai| async move {
|
||||||
let coin1 = Coin::Monero;
|
let coin1 = ExternalCoin::Monero;
|
||||||
let coin2 = Coin::Ether;
|
let coin2 = ExternalCoin::Ether;
|
||||||
let pair = insecure_pair_from_name("Ferdie");
|
let pair = insecure_pair_from_name("Ferdie");
|
||||||
let mut coin1_batch_id = 0;
|
let mut coin1_batch_id = 0;
|
||||||
let mut coin2_batch_id = 0;
|
let mut coin2_batch_id = 0;
|
||||||
@@ -262,8 +280,7 @@ serai_test!(
|
|||||||
// mint coins
|
// mint coins
|
||||||
mint_coin(
|
mint_coin(
|
||||||
&serai,
|
&serai,
|
||||||
Balance { coin: coin1, amount: Amount(10_000_000_000_000_000) },
|
ExternalBalance { coin: coin1, amount: Amount(10_000_000_000_000_000) },
|
||||||
NetworkId::Monero,
|
|
||||||
coin1_batch_id,
|
coin1_batch_id,
|
||||||
pair.clone().public().into(),
|
pair.clone().public().into(),
|
||||||
)
|
)
|
||||||
@@ -271,8 +288,7 @@ serai_test!(
|
|||||||
coin1_batch_id += 1;
|
coin1_batch_id += 1;
|
||||||
mint_coin(
|
mint_coin(
|
||||||
&serai,
|
&serai,
|
||||||
Balance { coin: coin2, amount: Amount(100_000_000_000_000) },
|
ExternalBalance { coin: coin2, amount: Amount(100_000_000_000_000) },
|
||||||
NetworkId::Ethereum,
|
|
||||||
coin2_batch_id,
|
coin2_batch_id,
|
||||||
pair.clone().public().into(),
|
pair.clone().public().into(),
|
||||||
)
|
)
|
||||||
@@ -305,18 +321,18 @@ serai_test!(
|
|||||||
let out_address = OutAddress::External(ExternalAddress::new(rand_bytes.clone()).unwrap());
|
let out_address = OutAddress::External(ExternalAddress::new(rand_bytes.clone()).unwrap());
|
||||||
|
|
||||||
// amount is the min out amount
|
// amount is the min out amount
|
||||||
let out_balance = Balance { coin: coin2, amount: Amount(1) };
|
let out_balance = Balance { coin: coin2.into(), amount: Amount(1) };
|
||||||
|
|
||||||
// now that we have our pools, we can try to swap
|
// now that we have our pools, we can try to swap
|
||||||
let mut block_hash = BlockHash([0; 32]);
|
let mut block_hash = BlockHash([0; 32]);
|
||||||
OsRng.fill_bytes(&mut block_hash.0);
|
OsRng.fill_bytes(&mut block_hash.0);
|
||||||
let batch = Batch {
|
let batch = Batch {
|
||||||
network: NetworkId::Monero,
|
network: coin1.network(),
|
||||||
id: coin1_batch_id,
|
id: coin1_batch_id,
|
||||||
external_network_block_hash: block_hash,
|
external_network_block_hash: block_hash,
|
||||||
instructions: vec![InInstructionWithBalance {
|
instructions: vec![InInstructionWithBalance {
|
||||||
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address)),
|
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address)),
|
||||||
balance: Balance { coin: coin1, amount: Amount(200_000_000_000_000) },
|
balance: ExternalBalance { coin: coin1, amount: Amount(200_000_000_000_000) },
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -325,7 +341,7 @@ serai_test!(
|
|||||||
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
||||||
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||||
|
|
||||||
let path = BoundedVec::try_from(vec![coin1, Coin::Serai, coin2]).unwrap();
|
let path = BoundedVec::try_from(vec![coin1.into(), Coin::Serai, coin2.into()]).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events,
|
events,
|
||||||
vec![DexEvent::SwapExecuted {
|
vec![DexEvent::SwapExecuted {
|
||||||
@@ -345,18 +361,18 @@ serai_test!(
|
|||||||
OutAddress::Serai(SeraiAddress::new(rand_bytes.clone().try_into().unwrap()));
|
OutAddress::Serai(SeraiAddress::new(rand_bytes.clone().try_into().unwrap()));
|
||||||
|
|
||||||
// amount is the min out amount
|
// amount is the min out amount
|
||||||
let out_balance = Balance { coin: coin1, amount: Amount(1) };
|
let out_balance = Balance { coin: coin1.into(), amount: Amount(1) };
|
||||||
|
|
||||||
// now that we have our pools, we can try to swap
|
// now that we have our pools, we can try to swap
|
||||||
let mut block_hash = BlockHash([0; 32]);
|
let mut block_hash = BlockHash([0; 32]);
|
||||||
OsRng.fill_bytes(&mut block_hash.0);
|
OsRng.fill_bytes(&mut block_hash.0);
|
||||||
let batch = Batch {
|
let batch = Batch {
|
||||||
network: NetworkId::Ethereum,
|
network: coin2.network(),
|
||||||
id: coin2_batch_id,
|
id: coin2_batch_id,
|
||||||
external_network_block_hash: block_hash,
|
external_network_block_hash: block_hash,
|
||||||
instructions: vec![InInstructionWithBalance {
|
instructions: vec![InInstructionWithBalance {
|
||||||
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address.clone())),
|
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address.clone())),
|
||||||
balance: Balance { coin: coin2, amount: Amount(200_000_000_000) },
|
balance: ExternalBalance { coin: coin2, amount: Amount(200_000_000_000) },
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -364,7 +380,7 @@ serai_test!(
|
|||||||
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
||||||
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||||
|
|
||||||
let path = BoundedVec::try_from(vec![coin2, Coin::Serai, coin1]).unwrap();
|
let path = BoundedVec::try_from(vec![coin2.into(), Coin::Serai, coin1.into()]).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events,
|
events,
|
||||||
vec![DexEvent::SwapExecuted {
|
vec![DexEvent::SwapExecuted {
|
||||||
@@ -389,12 +405,12 @@ serai_test!(
|
|||||||
let mut block_hash = BlockHash([0; 32]);
|
let mut block_hash = BlockHash([0; 32]);
|
||||||
OsRng.fill_bytes(&mut block_hash.0);
|
OsRng.fill_bytes(&mut block_hash.0);
|
||||||
let batch = Batch {
|
let batch = Batch {
|
||||||
network: NetworkId::Monero,
|
network: coin1.network(),
|
||||||
id: coin1_batch_id,
|
id: coin1_batch_id,
|
||||||
external_network_block_hash: block_hash,
|
external_network_block_hash: block_hash,
|
||||||
instructions: vec![InInstructionWithBalance {
|
instructions: vec![InInstructionWithBalance {
|
||||||
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address.clone())),
|
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address.clone())),
|
||||||
balance: Balance { coin: coin1, amount: Amount(100_000_000_000_000) },
|
balance: ExternalBalance { coin: coin1, amount: Amount(100_000_000_000_000) },
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -402,7 +418,7 @@ serai_test!(
|
|||||||
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
let mut events = serai.as_of(block).dex().events().await.unwrap();
|
||||||
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||||
|
|
||||||
let path = BoundedVec::try_from(vec![coin1, Coin::Serai]).unwrap();
|
let path = BoundedVec::try_from(vec![coin1.into(), Coin::Serai]).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events,
|
events,
|
||||||
vec![DexEvent::SwapExecuted {
|
vec![DexEvent::SwapExecuted {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use serai_client::{primitives::NetworkId, Serai};
|
use serai_client::{primitives::ExternalNetworkId, Serai};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn dht() {
|
async fn dht() {
|
||||||
@@ -44,7 +44,7 @@ async fn dht() {
|
|||||||
assert!(!Serai::new(serai_rpc.clone())
|
assert!(!Serai::new(serai_rpc.clone())
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.p2p_validators(NetworkId::Bitcoin)
|
.p2p_validators(ExternalNetworkId::Bitcoin.into())
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.is_empty());
|
.is_empty());
|
||||||
|
|||||||
@@ -13,10 +13,7 @@ use serai_abi::{
|
|||||||
in_instructions::primitives::Batch,
|
in_instructions::primitives::Batch,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serai_client::{
|
use serai_client::Serai;
|
||||||
primitives::{Amount, NetworkId, Balance},
|
|
||||||
Serai,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
use common::{genesis_liquidity::set_up_genesis, in_instructions::provide_batch};
|
use common::{genesis_liquidity::set_up_genesis, in_instructions::provide_batch};
|
||||||
@@ -27,20 +24,19 @@ serai_test_fast_epoch!(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
async fn send_batches(serai: &Serai, ids: &mut HashMap<NetworkId, u32>) {
|
async fn send_batches(serai: &Serai, ids: &mut HashMap<ExternalNetworkId, u32>) {
|
||||||
for network in NETWORKS {
|
for network in EXTERNAL_NETWORKS {
|
||||||
if network != NetworkId::Serai {
|
// set up batch id
|
||||||
// set up batch id
|
ids
|
||||||
ids
|
.entry(network)
|
||||||
.entry(network)
|
.and_modify(|v| {
|
||||||
.and_modify(|v| {
|
*v += 1;
|
||||||
*v += 1;
|
})
|
||||||
})
|
.or_insert(0);
|
||||||
.or_insert(0);
|
|
||||||
|
|
||||||
// set up block hash
|
// set up block hash
|
||||||
let mut block = BlockHash([0; 32]);
|
let mut block = BlockHash([0; 32]);
|
||||||
OsRng.fill_bytes(&mut block.0);
|
OsRng.fill_bytes(&mut block.0);
|
||||||
|
|
||||||
provide_batch(
|
provide_batch(
|
||||||
serai,
|
serai,
|
||||||
@@ -58,9 +54,12 @@ async fn send_batches(serai: &Serai, ids: &mut HashMap<NetworkId, u32>) {
|
|||||||
|
|
||||||
async fn test_emissions(serai: Serai) {
|
async fn test_emissions(serai: Serai) {
|
||||||
// set up the genesis
|
// set up the genesis
|
||||||
let coins = COINS.into_iter().filter(|c| *c != Coin::native()).collect::<Vec<_>>();
|
let values = HashMap::from([
|
||||||
let values = HashMap::from([(Coin::Monero, 184100), (Coin::Ether, 4785000), (Coin::Dai, 1500)]);
|
(ExternalCoin::Monero, 184100),
|
||||||
let (_, mut batch_ids) = set_up_genesis(&serai, &coins, &values).await;
|
(ExternalCoin::Ether, 4785000),
|
||||||
|
(ExternalCoin::Dai, 1500),
|
||||||
|
]);
|
||||||
|
let (_, mut batch_ids) = set_up_genesis(&serai, &values).await;
|
||||||
|
|
||||||
// wait until genesis is complete
|
// wait until genesis is complete
|
||||||
let mut genesis_complete_block = None;
|
let mut genesis_complete_block = None;
|
||||||
@@ -153,7 +152,7 @@ async fn test_emissions(serai: Serai) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the required stake in terms SRI for a given `Balance`.
|
/// Returns the required stake in terms SRI for a given `Balance`.
|
||||||
async fn required_stake(serai: &TemporalSerai<'_>, balance: Balance) -> u64 {
|
async fn required_stake(serai: &TemporalSerai<'_>, balance: ExternalBalance) -> u64 {
|
||||||
// This is inclusive to an increase in accuracy
|
// This is inclusive to an increase in accuracy
|
||||||
let sri_per_coin = serai.dex().oracle_value(balance.coin).await.unwrap().unwrap_or(Amount(0));
|
let sri_per_coin = serai.dex().oracle_value(balance.coin).await.unwrap().unwrap_or(Amount(0));
|
||||||
|
|
||||||
@@ -217,18 +216,14 @@ async fn get_distances(
|
|||||||
// we can check the supply to see how much coin hence liability we have.
|
// we can check the supply to see how much coin hence liability we have.
|
||||||
let mut distances: HashMap<NetworkId, u64> = HashMap::new();
|
let mut distances: HashMap<NetworkId, u64> = HashMap::new();
|
||||||
let mut total_distance = 0;
|
let mut total_distance = 0;
|
||||||
for n in NETWORKS {
|
for n in EXTERNAL_NETWORKS {
|
||||||
if n == NetworkId::Serai {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut required = 0;
|
let mut required = 0;
|
||||||
for c in n.coins() {
|
for c in n.coins() {
|
||||||
let amount = serai.coins().coin_supply(*c).await.unwrap();
|
let amount = serai.coins().coin_supply(c.into()).await.unwrap();
|
||||||
required += required_stake(serai, Balance { coin: *c, amount }).await;
|
required += required_stake(serai, ExternalBalance { coin: c, amount }).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut current = *current_stake.get(&n).unwrap();
|
let mut current = *current_stake.get(&n.into()).unwrap();
|
||||||
if current > required {
|
if current > required {
|
||||||
current = required;
|
current = required;
|
||||||
}
|
}
|
||||||
@@ -236,7 +231,7 @@ async fn get_distances(
|
|||||||
let distance = required - current;
|
let distance = required - current;
|
||||||
total_distance += distance;
|
total_distance += distance;
|
||||||
|
|
||||||
distances.insert(n, distance);
|
distances.insert(n.into(), distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add serai network portion(20%)
|
// add serai network portion(20%)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::{time::Duration, collections::HashMap};
|
|||||||
|
|
||||||
use serai_client::Serai;
|
use serai_client::Serai;
|
||||||
|
|
||||||
use serai_abi::primitives::{Coin, COINS, Amount, GENESIS_SRI};
|
use serai_abi::primitives::{Amount, Coin, ExternalCoin, COINS, EXTERNAL_COINS, GENESIS_SRI};
|
||||||
|
|
||||||
use serai_client::genesis_liquidity::primitives::{
|
use serai_client::genesis_liquidity::primitives::{
|
||||||
GENESIS_LIQUIDITY_ACCOUNT, INITIAL_GENESIS_LP_SHARES,
|
GENESIS_LIQUIDITY_ACCOUNT, INITIAL_GENESIS_LP_SHARES,
|
||||||
@@ -19,9 +19,12 @@ serai_test_fast_epoch!(
|
|||||||
|
|
||||||
pub async fn test_genesis_liquidity(serai: Serai) {
|
pub async fn test_genesis_liquidity(serai: Serai) {
|
||||||
// set up the genesis
|
// set up the genesis
|
||||||
let coins = COINS.into_iter().filter(|c| *c != Coin::native()).collect::<Vec<_>>();
|
let values = HashMap::from([
|
||||||
let values = HashMap::from([(Coin::Monero, 184100), (Coin::Ether, 4785000), (Coin::Dai, 1500)]);
|
(ExternalCoin::Monero, 184100),
|
||||||
let (accounts, _) = set_up_genesis(&serai, &coins, &values).await;
|
(ExternalCoin::Ether, 4785000),
|
||||||
|
(ExternalCoin::Dai, 1500),
|
||||||
|
]);
|
||||||
|
let (accounts, _) = set_up_genesis(&serai, &values).await;
|
||||||
|
|
||||||
// wait until genesis is complete
|
// wait until genesis is complete
|
||||||
while serai
|
while serai
|
||||||
@@ -55,9 +58,9 @@ pub async fn test_genesis_liquidity(serai: Serai) {
|
|||||||
// check pools has proper liquidity
|
// check pools has proper liquidity
|
||||||
let mut pool_amounts = HashMap::new();
|
let mut pool_amounts = HashMap::new();
|
||||||
let mut total_value = 0u128;
|
let mut total_value = 0u128;
|
||||||
for coin in coins.clone() {
|
for coin in EXTERNAL_COINS {
|
||||||
let total_coin = accounts[&coin].iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0));
|
let total_coin = accounts[&coin].iter().fold(0u128, |acc, value| acc + u128::from(value.1 .0));
|
||||||
let value = if coin != Coin::Bitcoin {
|
let value = if coin != ExternalCoin::Bitcoin {
|
||||||
(total_coin * u128::from(values[&coin])) / 10u128.pow(coin.decimals())
|
(total_coin * u128::from(values[&coin])) / 10u128.pow(coin.decimals())
|
||||||
} else {
|
} else {
|
||||||
total_coin
|
total_coin
|
||||||
@@ -69,8 +72,8 @@ pub async fn test_genesis_liquidity(serai: Serai) {
|
|||||||
|
|
||||||
// check distributed SRI per pool
|
// check distributed SRI per pool
|
||||||
let mut total_sri_distributed = 0u128;
|
let mut total_sri_distributed = 0u128;
|
||||||
for coin in coins.clone() {
|
for coin in EXTERNAL_COINS {
|
||||||
let sri = if coin == *COINS.last().unwrap() {
|
let sri = if coin == *EXTERNAL_COINS.last().unwrap() {
|
||||||
u128::from(GENESIS_SRI).checked_sub(total_sri_distributed).unwrap()
|
u128::from(GENESIS_SRI).checked_sub(total_sri_distributed).unwrap()
|
||||||
} else {
|
} else {
|
||||||
(pool_amounts[&coin].1 * u128::from(GENESIS_SRI)) / total_value
|
(pool_amounts[&coin].1 * u128::from(GENESIS_SRI)) / total_value
|
||||||
@@ -83,7 +86,7 @@ pub async fn test_genesis_liquidity(serai: Serai) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// check each liquidity provider got liquidity tokens proportional to their value
|
// check each liquidity provider got liquidity tokens proportional to their value
|
||||||
for coin in coins {
|
for coin in EXTERNAL_COINS {
|
||||||
let liq_supply = serai.genesis_liquidity().supply(coin).await.unwrap();
|
let liq_supply = serai.genesis_liquidity().supply(coin).await.unwrap();
|
||||||
for (acc, amount) in &accounts[&coin] {
|
for (acc, amount) in &accounts[&coin] {
|
||||||
let acc_liq_shares = serai.genesis_liquidity().liquidity(acc, coin).await.unwrap().shares;
|
let acc_liq_shares = serai.genesis_liquidity().liquidity(acc, coin).await.unwrap().shares;
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ use serai_client::{
|
|||||||
insecure_pair_from_name,
|
insecure_pair_from_name,
|
||||||
},
|
},
|
||||||
validator_sets::{
|
validator_sets::{
|
||||||
primitives::{Session, ValidatorSet, KeyPair},
|
primitives::{Session, ValidatorSet, ExternalValidatorSet, KeyPair},
|
||||||
ValidatorSetsEvent,
|
ValidatorSetsEvent,
|
||||||
},
|
},
|
||||||
in_instructions::{
|
in_instructions::{
|
||||||
primitives::{Batch, SignedBatch, batch_message},
|
primitives::{Batch, SignedBatch, batch_message},
|
||||||
SeraiInInstructions,
|
SeraiInInstructions,
|
||||||
},
|
},
|
||||||
Amount, Serai,
|
Serai,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
@@ -59,8 +59,8 @@ async fn get_ordered_keys(serai: &Serai, network: NetworkId, accounts: &[Pair])
|
|||||||
|
|
||||||
serai_test!(
|
serai_test!(
|
||||||
set_keys_test: (|serai: Serai| async move {
|
set_keys_test: (|serai: Serai| async move {
|
||||||
let network = NetworkId::Bitcoin;
|
let network = ExternalNetworkId::Bitcoin;
|
||||||
let set = ValidatorSet { session: Session(0), network };
|
let set = ExternalValidatorSet { session: Session(0), network };
|
||||||
|
|
||||||
let pair = insecure_pair_from_name("Alice");
|
let pair = insecure_pair_from_name("Alice");
|
||||||
let public = pair.public();
|
let public = pair.public();
|
||||||
@@ -90,7 +90,7 @@ serai_test!(
|
|||||||
{
|
{
|
||||||
let vs_serai = serai.as_of_latest_finalized_block().await.unwrap();
|
let vs_serai = serai.as_of_latest_finalized_block().await.unwrap();
|
||||||
let vs_serai = vs_serai.validator_sets();
|
let vs_serai = vs_serai.validator_sets();
|
||||||
let participants = vs_serai.participants(set.network).await
|
let participants = vs_serai.participants(set.network.into()).await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -198,9 +198,9 @@ async fn validator_set_rotation() {
|
|||||||
// amounts for single key share per network
|
// amounts for single key share per network
|
||||||
let key_shares = HashMap::from([
|
let key_shares = HashMap::from([
|
||||||
(NetworkId::Serai, Amount(50_000 * 10_u64.pow(8))),
|
(NetworkId::Serai, Amount(50_000 * 10_u64.pow(8))),
|
||||||
(NetworkId::Bitcoin, Amount(1_000_000 * 10_u64.pow(8))),
|
(NetworkId::External(ExternalNetworkId::Bitcoin), Amount(1_000_000 * 10_u64.pow(8))),
|
||||||
(NetworkId::Monero, Amount(100_000 * 10_u64.pow(8))),
|
(NetworkId::External(ExternalNetworkId::Monero), Amount(100_000 * 10_u64.pow(8))),
|
||||||
(NetworkId::Ethereum, Amount(1_000_000 * 10_u64.pow(8))),
|
(NetworkId::External(ExternalNetworkId::Ethereum), Amount(1_000_000 * 10_u64.pow(8))),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// genesis participants per network
|
// genesis participants per network
|
||||||
@@ -209,9 +209,9 @@ async fn validator_set_rotation() {
|
|||||||
accounts[.. 4].to_vec().iter().map(|pair| pair.public()).collect::<Vec<_>>();
|
accounts[.. 4].to_vec().iter().map(|pair| pair.public()).collect::<Vec<_>>();
|
||||||
let mut participants = HashMap::from([
|
let mut participants = HashMap::from([
|
||||||
(NetworkId::Serai, default_participants.clone()),
|
(NetworkId::Serai, default_participants.clone()),
|
||||||
(NetworkId::Bitcoin, default_participants.clone()),
|
(NetworkId::External(ExternalNetworkId::Bitcoin), default_participants.clone()),
|
||||||
(NetworkId::Monero, default_participants.clone()),
|
(NetworkId::External(ExternalNetworkId::Monero), default_participants.clone()),
|
||||||
(NetworkId::Ethereum, default_participants),
|
(NetworkId::External(ExternalNetworkId::Ethereum), default_participants),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// test the set rotation
|
// test the set rotation
|
||||||
@@ -265,7 +265,8 @@ async fn validator_set_rotation() {
|
|||||||
|
|
||||||
// set the keys if it is an external set
|
// set the keys if it is an external set
|
||||||
if network != NetworkId::Serai {
|
if network != NetworkId::Serai {
|
||||||
let set = ValidatorSet { session: Session(0), network };
|
let set =
|
||||||
|
ExternalValidatorSet { session: Session(0), network: network.try_into().unwrap() };
|
||||||
let key_pair = get_random_key_pair();
|
let key_pair = get_random_key_pair();
|
||||||
let pairs = get_ordered_keys(&serai, network, &accounts).await;
|
let pairs = get_ordered_keys(&serai, network, &accounts).await;
|
||||||
set_keys(&serai, set, key_pair, &pairs).await;
|
set_keys(&serai, set, key_pair, &pairs).await;
|
||||||
@@ -293,7 +294,8 @@ async fn validator_set_rotation() {
|
|||||||
|
|
||||||
if network != NetworkId::Serai {
|
if network != NetworkId::Serai {
|
||||||
// set the keys if it is an external set
|
// set the keys if it is an external set
|
||||||
let set = ValidatorSet { session: Session(1), network };
|
let set =
|
||||||
|
ExternalValidatorSet { session: Session(1), network: network.try_into().unwrap() };
|
||||||
|
|
||||||
// we need the whole substrate key pair to sign the batch
|
// we need the whole substrate key pair to sign the batch
|
||||||
let (substrate_pair, key_pair) = {
|
let (substrate_pair, key_pair) = {
|
||||||
@@ -312,7 +314,7 @@ async fn validator_set_rotation() {
|
|||||||
let mut block_hash = BlockHash([0; 32]);
|
let mut block_hash = BlockHash([0; 32]);
|
||||||
OsRng.fill_bytes(&mut block_hash.0);
|
OsRng.fill_bytes(&mut block_hash.0);
|
||||||
let batch =
|
let batch =
|
||||||
Batch { network, id: 0, external_network_block_hash: block_hash, instructions: vec![] };
|
Batch { network: network.try_into().unwrap(), id: 0, external_network_block_hash: block_hash, instructions: vec![] };
|
||||||
publish_tx(
|
publish_tx(
|
||||||
&serai,
|
&serai,
|
||||||
&SeraiInInstructions::execute_batch(SignedBatch {
|
&SeraiInInstructions::execute_batch(SignedBatch {
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", d
|
|||||||
serai-primitives = { path = "../../primitives", default-features = false, features = ["serde"] }
|
serai-primitives = { path = "../../primitives", default-features = false, features = ["serde"] }
|
||||||
coins-primitives = { package = "serai-coins-primitives", path = "../primitives", default-features = false }
|
coins-primitives = { package = "serai-coins-primitives", path = "../primitives", default-features = false }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
sp-io = { git = "https://github.com/serai-dex/substrate", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = [
|
std = [
|
||||||
"frame-system/std",
|
"frame-system/std",
|
||||||
@@ -49,8 +52,12 @@ std = [
|
|||||||
"coins-primitives/std",
|
"coins-primitives/std",
|
||||||
]
|
]
|
||||||
|
|
||||||
# TODO
|
try-runtime = [
|
||||||
try-runtime = []
|
"frame-system/try-runtime",
|
||||||
|
"frame-support/try-runtime",
|
||||||
|
|
||||||
|
"sp-runtime/try-runtime",
|
||||||
|
]
|
||||||
|
|
||||||
runtime-benchmarks = [
|
runtime-benchmarks = [
|
||||||
"frame-system/runtime-benchmarks",
|
"frame-system/runtime-benchmarks",
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
use serai_primitives::{Coin, SubstrateAmount, Balance};
|
#[cfg(test)]
|
||||||
|
mod mock;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
use serai_primitives::{Balance, Coin, ExternalBalance, SubstrateAmount};
|
||||||
|
|
||||||
pub trait AllowMint {
|
pub trait AllowMint {
|
||||||
fn is_allowed(balance: &Balance) -> bool;
|
fn is_allowed(balance: &ExternalBalance) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AllowMint for () {
|
impl AllowMint for () {
|
||||||
fn is_allowed(_: &Balance) -> bool {
|
fn is_allowed(_: &ExternalBalance) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +167,10 @@ pub mod pallet {
|
|||||||
pub fn mint(to: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
pub fn mint(to: Public, balance: Balance) -> Result<(), Error<T, I>> {
|
||||||
// If the coin isn't Serai, which we're always allowed to mint, and the mint isn't explicitly
|
// If the coin isn't Serai, which we're always allowed to mint, and the mint isn't explicitly
|
||||||
// allowed, error
|
// allowed, error
|
||||||
if (balance.coin != Coin::Serai) && (!T::AllowMint::is_allowed(&balance)) {
|
if !ExternalCoin::try_from(balance.coin)
|
||||||
|
.map(|coin| T::AllowMint::is_allowed(&ExternalBalance { coin, amount: balance.amount }))
|
||||||
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
Err(Error::<T, I>::MintNotAllowed)?;
|
Err(Error::<T, I>::MintNotAllowed)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,22 +239,18 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Burn `balance` with `OutInstructionWithBalance` from the caller.
|
/// Burn `balance` with `OutInstructionWithBalance` from the caller.
|
||||||
/// Errors if called for SRI or Instance1 instance of this pallet.
|
|
||||||
#[pallet::call_index(2)]
|
#[pallet::call_index(2)]
|
||||||
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
||||||
pub fn burn_with_instruction(
|
pub fn burn_with_instruction(
|
||||||
origin: OriginFor<T>,
|
origin: OriginFor<T>,
|
||||||
instruction: OutInstructionWithBalance,
|
instruction: OutInstructionWithBalance,
|
||||||
) -> DispatchResult {
|
) -> DispatchResult {
|
||||||
if instruction.balance.coin == Coin::Serai {
|
|
||||||
Err(Error::<T, I>::BurnWithInstructionNotAllowed)?;
|
|
||||||
}
|
|
||||||
if TypeId::of::<I>() == TypeId::of::<LiquidityTokensInstance>() {
|
if TypeId::of::<I>() == TypeId::of::<LiquidityTokensInstance>() {
|
||||||
Err(Error::<T, I>::BurnWithInstructionNotAllowed)?;
|
Err(Error::<T, I>::BurnWithInstructionNotAllowed)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let from = ensure_signed(origin)?;
|
let from = ensure_signed(origin)?;
|
||||||
Self::burn_internal(from, instruction.balance)?;
|
Self::burn_internal(from, instruction.balance.into())?;
|
||||||
Self::deposit_event(Event::BurnWithInstruction { from, instruction });
|
Self::deposit_event(Event::BurnWithInstruction { from, instruction });
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
70
substrate/coins/pallet/src/mock.rs
Normal file
70
substrate/coins/pallet/src/mock.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
//! Test environment for Coins pallet.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use frame_support::{
|
||||||
|
construct_runtime,
|
||||||
|
traits::{ConstU32, ConstU64},
|
||||||
|
};
|
||||||
|
|
||||||
|
use sp_core::{H256, sr25519::Public};
|
||||||
|
use sp_runtime::{
|
||||||
|
traits::{BlakeTwo256, IdentityLookup},
|
||||||
|
BuildStorage,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate as coins;
|
||||||
|
|
||||||
|
type Block = frame_system::mocking::MockBlock<Test>;
|
||||||
|
|
||||||
|
construct_runtime!(
|
||||||
|
pub enum Test
|
||||||
|
{
|
||||||
|
System: frame_system,
|
||||||
|
Coins: coins,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
impl frame_system::Config for Test {
|
||||||
|
type BaseCallFilter = frame_support::traits::Everything;
|
||||||
|
type BlockWeights = ();
|
||||||
|
type BlockLength = ();
|
||||||
|
type RuntimeOrigin = RuntimeOrigin;
|
||||||
|
type RuntimeCall = RuntimeCall;
|
||||||
|
type Nonce = u64;
|
||||||
|
type Hash = H256;
|
||||||
|
type Hashing = BlakeTwo256;
|
||||||
|
type AccountId = Public;
|
||||||
|
type Lookup = IdentityLookup<Self::AccountId>;
|
||||||
|
type Block = Block;
|
||||||
|
type RuntimeEvent = RuntimeEvent;
|
||||||
|
type BlockHashCount = ConstU64<250>;
|
||||||
|
type DbWeight = ();
|
||||||
|
type Version = ();
|
||||||
|
type PalletInfo = PalletInfo;
|
||||||
|
type AccountData = ();
|
||||||
|
type OnNewAccount = ();
|
||||||
|
type OnKilledAccount = ();
|
||||||
|
type SystemWeightInfo = ();
|
||||||
|
type SS58Prefix = ();
|
||||||
|
type OnSetCode = ();
|
||||||
|
type MaxConsumers = ConstU32<16>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config for Test {
|
||||||
|
type RuntimeEvent = RuntimeEvent;
|
||||||
|
|
||||||
|
type AllowMint = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
|
||||||
|
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||||
|
|
||||||
|
crate::GenesisConfig::<Test> { accounts: vec![], _ignore: Default::default() }
|
||||||
|
.assimilate_storage(&mut t)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut ext = sp_io::TestExternalities::new(t);
|
||||||
|
ext.execute_with(|| System::set_block_number(0));
|
||||||
|
ext
|
||||||
|
}
|
||||||
129
substrate/coins/pallet/src/tests.rs
Normal file
129
substrate/coins/pallet/src/tests.rs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
use crate::{mock::*, primitives::*};
|
||||||
|
|
||||||
|
use frame_system::RawOrigin;
|
||||||
|
use sp_core::Pair;
|
||||||
|
|
||||||
|
use serai_primitives::*;
|
||||||
|
|
||||||
|
pub type CoinsEvent = crate::Event<Test, ()>;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mint() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
// minting u64::MAX should work
|
||||||
|
let coin = Coin::Serai;
|
||||||
|
let to = insecure_pair_from_name("random1").public();
|
||||||
|
let balance = Balance { coin, amount: Amount(u64::MAX) };
|
||||||
|
|
||||||
|
Coins::mint(to, balance).unwrap();
|
||||||
|
assert_eq!(Coins::balance(to, coin), balance.amount);
|
||||||
|
|
||||||
|
// minting more should fail
|
||||||
|
assert!(Coins::mint(to, Balance { coin, amount: Amount(1) }).is_err());
|
||||||
|
|
||||||
|
// supply now should be equal to sum of the accounts balance sum
|
||||||
|
assert_eq!(Coins::supply(coin), balance.amount.0);
|
||||||
|
|
||||||
|
// test events
|
||||||
|
let mint_events = System::events()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|event| {
|
||||||
|
if let RuntimeEvent::Coins(e) = &event.event {
|
||||||
|
if matches!(e, CoinsEvent::Mint { .. }) {
|
||||||
|
Some(e.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(mint_events, vec![CoinsEvent::Mint { to, balance }]);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn burn_with_instruction() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
// mint some coin
|
||||||
|
let coin = Coin::External(ExternalCoin::Bitcoin);
|
||||||
|
let to = insecure_pair_from_name("random1").public();
|
||||||
|
let balance = Balance { coin, amount: Amount(10 * 10u64.pow(coin.decimals())) };
|
||||||
|
|
||||||
|
Coins::mint(to, balance).unwrap();
|
||||||
|
assert_eq!(Coins::balance(to, coin), balance.amount);
|
||||||
|
assert_eq!(Coins::supply(coin), balance.amount.0);
|
||||||
|
|
||||||
|
// we shouldn't be able to burn more than what we have
|
||||||
|
let mut instruction = OutInstructionWithBalance {
|
||||||
|
instruction: OutInstruction { address: ExternalAddress::new(vec![]).unwrap(), data: None },
|
||||||
|
balance: ExternalBalance {
|
||||||
|
coin: coin.try_into().unwrap(),
|
||||||
|
amount: Amount(balance.amount.0 + 1),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
Coins::burn_with_instruction(RawOrigin::Signed(to).into(), instruction.clone()).is_err()
|
||||||
|
);
|
||||||
|
|
||||||
|
// it should now work
|
||||||
|
instruction.balance.amount = balance.amount;
|
||||||
|
Coins::burn_with_instruction(RawOrigin::Signed(to).into(), instruction.clone()).unwrap();
|
||||||
|
|
||||||
|
// balance & supply now should be back to 0
|
||||||
|
assert_eq!(Coins::balance(to, coin), Amount(0));
|
||||||
|
assert_eq!(Coins::supply(coin), 0);
|
||||||
|
|
||||||
|
let burn_events = System::events()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|event| {
|
||||||
|
if let RuntimeEvent::Coins(e) = &event.event {
|
||||||
|
if matches!(e, CoinsEvent::BurnWithInstruction { .. }) {
|
||||||
|
Some(e.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(burn_events, vec![CoinsEvent::BurnWithInstruction { from: to, instruction }]);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transfer() {
|
||||||
|
new_test_ext().execute_with(|| {
|
||||||
|
// mint some coin
|
||||||
|
let coin = Coin::External(ExternalCoin::Bitcoin);
|
||||||
|
let from = insecure_pair_from_name("random1").public();
|
||||||
|
let balance = Balance { coin, amount: Amount(10 * 10u64.pow(coin.decimals())) };
|
||||||
|
|
||||||
|
Coins::mint(from, balance).unwrap();
|
||||||
|
assert_eq!(Coins::balance(from, coin), balance.amount);
|
||||||
|
assert_eq!(Coins::supply(coin), balance.amount.0);
|
||||||
|
|
||||||
|
// we can't send more than what we have
|
||||||
|
let to = insecure_pair_from_name("random2").public();
|
||||||
|
assert!(Coins::transfer(
|
||||||
|
RawOrigin::Signed(from).into(),
|
||||||
|
to,
|
||||||
|
Balance { coin, amount: Amount(balance.amount.0 + 1) }
|
||||||
|
)
|
||||||
|
.is_err());
|
||||||
|
|
||||||
|
// we can send it all
|
||||||
|
Coins::transfer(RawOrigin::Signed(from).into(), to, balance).unwrap();
|
||||||
|
|
||||||
|
// check the balances
|
||||||
|
assert_eq!(Coins::balance(from, coin), Amount(0));
|
||||||
|
assert_eq!(Coins::balance(to, coin), balance.amount);
|
||||||
|
|
||||||
|
// supply shouldn't change
|
||||||
|
assert_eq!(Coins::supply(coin), balance.amount.0);
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ use serde::{Serialize, Deserialize};
|
|||||||
use scale::{Encode, Decode, MaxEncodedLen};
|
use scale::{Encode, Decode, MaxEncodedLen};
|
||||||
use scale_info::TypeInfo;
|
use scale_info::TypeInfo;
|
||||||
|
|
||||||
use serai_primitives::{Balance, SeraiAddress, ExternalAddress, system_address};
|
use serai_primitives::{ExternalBalance, SeraiAddress, ExternalAddress, system_address};
|
||||||
|
|
||||||
pub const FEE_ACCOUNT: SeraiAddress = system_address(b"Coins-fees");
|
pub const FEE_ACCOUNT: SeraiAddress = system_address(b"Coins-fees");
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ pub struct OutInstruction {
|
|||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
pub struct OutInstructionWithBalance {
|
pub struct OutInstructionWithBalance {
|
||||||
pub instruction: OutInstruction,
|
pub instruction: OutInstruction,
|
||||||
pub balance: Balance,
|
pub balance: ExternalBalance,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup
|
|||||||
|
|
||||||
type LiquidityTokens<T> = coins_pallet::Pallet<T, coins_pallet::Instance1>;
|
type LiquidityTokens<T> = coins_pallet::Pallet<T, coins_pallet::Instance1>;
|
||||||
|
|
||||||
fn create_coin<T: Config>(coin: &Coin) -> (T::AccountId, AccountIdLookupOf<T>) {
|
fn create_coin<T: Config>(coin: &ExternalCoin) -> (T::AccountId, AccountIdLookupOf<T>) {
|
||||||
let caller: T::AccountId = whitelisted_caller();
|
let caller: T::AccountId = whitelisted_caller();
|
||||||
let caller_lookup = T::Lookup::unlookup(caller);
|
let caller_lookup = T::Lookup::unlookup(caller);
|
||||||
assert_ok!(Coins::<T>::mint(
|
assert_ok!(Coins::<T>::mint(
|
||||||
@@ -47,12 +47,14 @@ fn create_coin<T: Config>(coin: &Coin) -> (T::AccountId, AccountIdLookupOf<T>) {
|
|||||||
));
|
));
|
||||||
assert_ok!(Coins::<T>::mint(
|
assert_ok!(Coins::<T>::mint(
|
||||||
caller,
|
caller,
|
||||||
Balance { coin: *coin, amount: Amount(INITIAL_COIN_BALANCE) }
|
Balance { coin: (*coin).into(), amount: Amount(INITIAL_COIN_BALANCE) }
|
||||||
));
|
));
|
||||||
(caller, caller_lookup)
|
(caller, caller_lookup)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_coin_and_pool<T: Config>(coin: &Coin) -> (Coin, T::AccountId, AccountIdLookupOf<T>) {
|
fn create_coin_and_pool<T: Config>(
|
||||||
|
coin: &ExternalCoin,
|
||||||
|
) -> (ExternalCoin, T::AccountId, AccountIdLookupOf<T>) {
|
||||||
let (caller, caller_lookup) = create_coin::<T>(coin);
|
let (caller, caller_lookup) = create_coin::<T>(coin);
|
||||||
assert_ok!(Dex::<T>::create_pool(*coin));
|
assert_ok!(Dex::<T>::create_pool(*coin));
|
||||||
|
|
||||||
@@ -62,7 +64,7 @@ fn create_coin_and_pool<T: Config>(coin: &Coin) -> (Coin, T::AccountId, AccountI
|
|||||||
benchmarks! {
|
benchmarks! {
|
||||||
add_liquidity {
|
add_liquidity {
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Bitcoin;
|
let coin2 = ExternalCoin::Bitcoin;
|
||||||
let (lp_token, caller, _) = create_coin_and_pool::<T>(&coin2);
|
let (lp_token, caller, _) = create_coin_and_pool::<T>(&coin2);
|
||||||
let add_amount: u64 = 1000;
|
let add_amount: u64 = 1000;
|
||||||
}: _(
|
}: _(
|
||||||
@@ -75,13 +77,13 @@ benchmarks! {
|
|||||||
caller
|
caller
|
||||||
)
|
)
|
||||||
verify {
|
verify {
|
||||||
let pool_id = Dex::<T>::get_pool_id(coin1, coin2).unwrap();
|
let pool_id = Dex::<T>::get_pool_id(coin1, coin2.into()).unwrap();
|
||||||
let lp_minted = Dex::<T>::calc_lp_amount_for_zero_supply(
|
let lp_minted = Dex::<T>::calc_lp_amount_for_zero_supply(
|
||||||
add_amount,
|
add_amount,
|
||||||
1000u64,
|
1000u64,
|
||||||
).unwrap();
|
).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
LiquidityTokens::<T>::balance(caller, lp_token).0,
|
LiquidityTokens::<T>::balance(caller, lp_token.into()).0,
|
||||||
lp_minted
|
lp_minted
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -91,7 +93,7 @@ benchmarks! {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Coins::<T>::balance(
|
Coins::<T>::balance(
|
||||||
Dex::<T>::get_pool_account(pool_id),
|
Dex::<T>::get_pool_account(pool_id),
|
||||||
Coin::Bitcoin,
|
ExternalCoin::Bitcoin.into(),
|
||||||
).0,
|
).0,
|
||||||
1000
|
1000
|
||||||
);
|
);
|
||||||
@@ -99,7 +101,7 @@ benchmarks! {
|
|||||||
|
|
||||||
remove_liquidity {
|
remove_liquidity {
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Monero;
|
let coin2 = ExternalCoin::Monero;
|
||||||
let (lp_token, caller, _) = create_coin_and_pool::<T>(&coin2);
|
let (lp_token, caller, _) = create_coin_and_pool::<T>(&coin2);
|
||||||
let add_amount: u64 = 100;
|
let add_amount: u64 = 100;
|
||||||
let lp_minted = Dex::<T>::calc_lp_amount_for_zero_supply(
|
let lp_minted = Dex::<T>::calc_lp_amount_for_zero_supply(
|
||||||
@@ -117,7 +119,7 @@ benchmarks! {
|
|||||||
0u64,
|
0u64,
|
||||||
caller,
|
caller,
|
||||||
)?;
|
)?;
|
||||||
let total_supply = LiquidityTokens::<T>::supply(lp_token);
|
let total_supply = LiquidityTokens::<T>::supply(Coin::from(lp_token));
|
||||||
}: _(
|
}: _(
|
||||||
SystemOrigin::Signed(caller),
|
SystemOrigin::Signed(caller),
|
||||||
coin2,
|
coin2,
|
||||||
@@ -127,7 +129,7 @@ benchmarks! {
|
|||||||
caller
|
caller
|
||||||
)
|
)
|
||||||
verify {
|
verify {
|
||||||
let new_total_supply = LiquidityTokens::<T>::supply(lp_token);
|
let new_total_supply = LiquidityTokens::<T>::supply(Coin::from(lp_token));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
new_total_supply,
|
new_total_supply,
|
||||||
total_supply - remove_lp_amount
|
total_supply - remove_lp_amount
|
||||||
@@ -136,8 +138,8 @@ benchmarks! {
|
|||||||
|
|
||||||
swap_exact_tokens_for_tokens {
|
swap_exact_tokens_for_tokens {
|
||||||
let native = Coin::native();
|
let native = Coin::native();
|
||||||
let coin1 = Coin::Bitcoin;
|
let coin1 = ExternalCoin::Bitcoin;
|
||||||
let coin2 = Coin::Ether;
|
let coin2 = ExternalCoin::Ether;
|
||||||
let (_, caller, _) = create_coin_and_pool::<T>(&coin1);
|
let (_, caller, _) = create_coin_and_pool::<T>(&coin1);
|
||||||
let (_, _) = create_coin::<T>(&coin2);
|
let (_, _) = create_coin::<T>(&coin2);
|
||||||
|
|
||||||
@@ -168,21 +170,21 @@ benchmarks! {
|
|||||||
caller,
|
caller,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let path = vec![coin1, native, coin2];
|
let path = vec![Coin::from(coin1), native, Coin::from(coin2)];
|
||||||
let path = BoundedVec::<_, T::MaxSwapPathLength>::try_from(path).unwrap();
|
let path = BoundedVec::<_, T::MaxSwapPathLength>::try_from(path).unwrap();
|
||||||
let native_balance = Coins::<T>::balance(caller, native).0;
|
let native_balance = Coins::<T>::balance(caller, native).0;
|
||||||
let coin1_balance = Coins::<T>::balance(caller, Coin::Bitcoin).0;
|
let coin1_balance = Coins::<T>::balance(caller, ExternalCoin::Bitcoin.into()).0;
|
||||||
}: _(SystemOrigin::Signed(caller), path, swap_amount, 1u64, caller)
|
}: _(SystemOrigin::Signed(caller), path, swap_amount, 1u64, caller)
|
||||||
verify {
|
verify {
|
||||||
let ed_bump = 2u64;
|
let ed_bump = 2u64;
|
||||||
let new_coin1_balance = Coins::<T>::balance(caller, Coin::Bitcoin).0;
|
let new_coin1_balance = Coins::<T>::balance(caller, ExternalCoin::Bitcoin.into()).0;
|
||||||
assert_eq!(new_coin1_balance, coin1_balance - 100u64);
|
assert_eq!(new_coin1_balance, coin1_balance - 100u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
swap_tokens_for_exact_tokens {
|
swap_tokens_for_exact_tokens {
|
||||||
let native = Coin::native();
|
let native = Coin::native();
|
||||||
let coin1 = Coin::Bitcoin;
|
let coin1 = ExternalCoin::Bitcoin;
|
||||||
let coin2 = Coin::Ether;
|
let coin2 = ExternalCoin::Ether;
|
||||||
let (_, caller, _) = create_coin_and_pool::<T>(&coin1);
|
let (_, caller, _) = create_coin_and_pool::<T>(&coin1);
|
||||||
let (_, _) = create_coin::<T>(&coin2);
|
let (_, _) = create_coin::<T>(&coin2);
|
||||||
|
|
||||||
@@ -208,10 +210,10 @@ benchmarks! {
|
|||||||
0u64,
|
0u64,
|
||||||
caller,
|
caller,
|
||||||
)?;
|
)?;
|
||||||
let path = vec![coin1, native, coin2];
|
let path = vec![Coin::from(coin1), native, Coin::from(coin2)];
|
||||||
|
|
||||||
let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap();
|
let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap();
|
||||||
let coin2_balance = Coins::<T>::balance(caller, Coin::Ether).0;
|
let coin2_balance = Coins::<T>::balance(caller, ExternalCoin::Ether.into()).0;
|
||||||
}: _(
|
}: _(
|
||||||
SystemOrigin::Signed(caller),
|
SystemOrigin::Signed(caller),
|
||||||
path.clone(),
|
path.clone(),
|
||||||
@@ -220,7 +222,7 @@ benchmarks! {
|
|||||||
caller
|
caller
|
||||||
)
|
)
|
||||||
verify {
|
verify {
|
||||||
let new_coin2_balance = Coins::<T>::balance(caller, Coin::Ether).0;
|
let new_coin2_balance = Coins::<T>::balance(caller, ExternalCoin::Ether.into()).0;
|
||||||
assert_eq!(new_coin2_balance, coin2_balance + 100u64);
|
assert_eq!(new_coin2_balance, coin2_balance + 100u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ mod tests;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod mock;
|
mod mock;
|
||||||
|
|
||||||
use frame_support::ensure;
|
use frame_support::{ensure, pallet_prelude::*, BoundedBTreeSet};
|
||||||
use frame_system::{
|
use frame_system::{
|
||||||
pallet_prelude::{BlockNumberFor, OriginFor},
|
pallet_prelude::{BlockNumberFor, OriginFor},
|
||||||
ensure_signed,
|
ensure_signed,
|
||||||
@@ -86,9 +86,12 @@ use frame_system::{
|
|||||||
|
|
||||||
pub use pallet::*;
|
pub use pallet::*;
|
||||||
|
|
||||||
use sp_runtime::{traits::TrailingZeroInput, DispatchError};
|
use sp_runtime::{
|
||||||
|
traits::{TrailingZeroInput, IntegerSquareRoot},
|
||||||
|
DispatchError,
|
||||||
|
};
|
||||||
|
|
||||||
use serai_primitives::{NetworkId, Coin, SubstrateAmount};
|
use serai_primitives::*;
|
||||||
|
|
||||||
use sp_std::prelude::*;
|
use sp_std::prelude::*;
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
@@ -103,20 +106,16 @@ pub use weights::WeightInfo;
|
|||||||
#[frame_support::pallet]
|
#[frame_support::pallet]
|
||||||
pub mod pallet {
|
pub mod pallet {
|
||||||
use super::*;
|
use super::*;
|
||||||
use frame_support::{pallet_prelude::*, BoundedBTreeSet};
|
|
||||||
|
|
||||||
use sp_core::sr25519::Public;
|
use sp_core::sr25519::Public;
|
||||||
use sp_runtime::traits::IntegerSquareRoot;
|
|
||||||
|
|
||||||
use coins_pallet::{Pallet as CoinsPallet, Config as CoinsConfig};
|
use coins_pallet::{Pallet as CoinsPallet, Config as CoinsConfig};
|
||||||
|
|
||||||
use serai_primitives::{Coin, Amount, Balance, SubstrateAmount, reverse_lexicographic_order};
|
|
||||||
|
|
||||||
/// Pool ID.
|
/// Pool ID.
|
||||||
///
|
///
|
||||||
/// The pool's `AccountId` is derived from this type. Any changes to the type may necessitate a
|
/// The pool's `AccountId` is derived from this type. Any changes to the type may necessitate a
|
||||||
/// migration.
|
/// migration.
|
||||||
pub type PoolId = Coin;
|
pub type PoolId = ExternalCoin;
|
||||||
|
|
||||||
/// LiquidityTokens Pallet as an instance of coins pallet.
|
/// LiquidityTokens Pallet as an instance of coins pallet.
|
||||||
pub type LiquidityTokens<T> = coins_pallet::Pallet<T, coins_pallet::Instance1>;
|
pub type LiquidityTokens<T> = coins_pallet::Pallet<T, coins_pallet::Instance1>;
|
||||||
@@ -164,7 +163,7 @@ pub mod pallet {
|
|||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
#[pallet::getter(fn spot_price_for_block)]
|
#[pallet::getter(fn spot_price_for_block)]
|
||||||
pub type SpotPriceForBlock<T: Config> =
|
pub type SpotPriceForBlock<T: Config> =
|
||||||
StorageDoubleMap<_, Identity, BlockNumberFor<T>, Identity, Coin, Amount, OptionQuery>;
|
StorageDoubleMap<_, Identity, BlockNumberFor<T>, Identity, ExternalCoin, Amount, OptionQuery>;
|
||||||
|
|
||||||
/// Moving window of prices from each block.
|
/// Moving window of prices from each block.
|
||||||
///
|
///
|
||||||
@@ -173,30 +172,32 @@ pub mod pallet {
|
|||||||
/// low to high.
|
/// low to high.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
pub type SpotPrices<T: Config> =
|
pub type SpotPrices<T: Config> =
|
||||||
StorageDoubleMap<_, Identity, Coin, Identity, [u8; 8], u16, OptionQuery>;
|
StorageDoubleMap<_, Identity, ExternalCoin, Identity, [u8; 8], u16, OptionQuery>;
|
||||||
|
|
||||||
// SpotPrices, yet with keys stored in reverse lexicographic order.
|
// SpotPrices, yet with keys stored in reverse lexicographic order.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
pub type ReverseSpotPrices<T: Config> =
|
pub type ReverseSpotPrices<T: Config> =
|
||||||
StorageDoubleMap<_, Identity, Coin, Identity, [u8; 8], (), OptionQuery>;
|
StorageDoubleMap<_, Identity, ExternalCoin, Identity, [u8; 8], (), OptionQuery>;
|
||||||
|
|
||||||
/// Current length of the `SpotPrices` map.
|
/// Current length of the `SpotPrices` map.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
pub type SpotPricesLength<T: Config> = StorageMap<_, Identity, Coin, u16, OptionQuery>;
|
pub type SpotPricesLength<T: Config> = StorageMap<_, Identity, ExternalCoin, u16, OptionQuery>;
|
||||||
|
|
||||||
/// Current position of the median within the `SpotPrices` map;
|
/// Current position of the median within the `SpotPrices` map;
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
pub type CurrentMedianPosition<T: Config> = StorageMap<_, Identity, Coin, u16, OptionQuery>;
|
pub type CurrentMedianPosition<T: Config> =
|
||||||
|
StorageMap<_, Identity, ExternalCoin, u16, OptionQuery>;
|
||||||
|
|
||||||
/// Current median price of the prices in the `SpotPrices` map at any given time.
|
/// Current median price of the prices in the `SpotPrices` map at any given time.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
#[pallet::getter(fn median_price)]
|
#[pallet::getter(fn median_price)]
|
||||||
pub type MedianPrice<T: Config> = StorageMap<_, Identity, Coin, Amount, OptionQuery>;
|
pub type MedianPrice<T: Config> = StorageMap<_, Identity, ExternalCoin, Amount, OptionQuery>;
|
||||||
|
|
||||||
/// The price used for evaluating economic security, which is the highest observed median price.
|
/// The price used for evaluating economic security, which is the highest observed median price.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
#[pallet::getter(fn security_oracle_value)]
|
#[pallet::getter(fn security_oracle_value)]
|
||||||
pub type SecurityOracleValue<T: Config> = StorageMap<_, Identity, Coin, Amount, OptionQuery>;
|
pub type SecurityOracleValue<T: Config> =
|
||||||
|
StorageMap<_, Identity, ExternalCoin, Amount, OptionQuery>;
|
||||||
|
|
||||||
/// Total swap volume of a given pool in terms of SRI.
|
/// Total swap volume of a given pool in terms of SRI.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
@@ -205,7 +206,7 @@ pub mod pallet {
|
|||||||
|
|
||||||
impl<T: Config> Pallet<T> {
|
impl<T: Config> Pallet<T> {
|
||||||
fn restore_median(
|
fn restore_median(
|
||||||
coin: Coin,
|
coin: ExternalCoin,
|
||||||
mut current_median_pos: u16,
|
mut current_median_pos: u16,
|
||||||
mut current_median: Amount,
|
mut current_median: Amount,
|
||||||
length: u16,
|
length: u16,
|
||||||
@@ -256,7 +257,7 @@ pub mod pallet {
|
|||||||
MedianPrice::<T>::set(coin, Some(current_median));
|
MedianPrice::<T>::set(coin, Some(current_median));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn insert_into_median(coin: Coin, amount: Amount) {
|
pub(crate) fn insert_into_median(coin: ExternalCoin, amount: Amount) {
|
||||||
let new_quantity_of_presences =
|
let new_quantity_of_presences =
|
||||||
SpotPrices::<T>::get(coin, amount.0.to_be_bytes()).unwrap_or(0) + 1;
|
SpotPrices::<T>::get(coin, amount.0.to_be_bytes()).unwrap_or(0) + 1;
|
||||||
SpotPrices::<T>::set(coin, amount.0.to_be_bytes(), Some(new_quantity_of_presences));
|
SpotPrices::<T>::set(coin, amount.0.to_be_bytes(), Some(new_quantity_of_presences));
|
||||||
@@ -286,7 +287,7 @@ pub mod pallet {
|
|||||||
Self::restore_median(coin, current_median_pos, current_median, new_length);
|
Self::restore_median(coin, current_median_pos, current_median, new_length);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn remove_from_median(coin: Coin, amount: Amount) {
|
pub(crate) fn remove_from_median(coin: ExternalCoin, amount: Amount) {
|
||||||
let mut current_median = MedianPrice::<T>::get(coin).unwrap();
|
let mut current_median = MedianPrice::<T>::get(coin).unwrap();
|
||||||
|
|
||||||
let mut current_median_pos = CurrentMedianPosition::<T>::get(coin).unwrap();
|
let mut current_median_pos = CurrentMedianPosition::<T>::get(coin).unwrap();
|
||||||
@@ -451,7 +452,7 @@ pub mod pallet {
|
|||||||
// insert the new price to our oracle window
|
// insert the new price to our oracle window
|
||||||
// The spot price for 1 coin, in atomic units, to SRI is used
|
// The spot price for 1 coin, in atomic units, to SRI is used
|
||||||
let sri_per_coin =
|
let sri_per_coin =
|
||||||
if let Ok((sri_balance, coin_balance)) = Self::get_reserves(&Coin::native(), &coin) {
|
if let Ok((sri_balance, coin_balance)) = Self::get_reserves(&Coin::Serai, &coin.into()) {
|
||||||
// We use 1 coin to handle rounding errors which may occur with atomic units
|
// We use 1 coin to handle rounding errors which may occur with atomic units
|
||||||
// If we used atomic units, any coin whose atomic unit is worth less than SRI's atomic
|
// If we used atomic units, any coin whose atomic unit is worth less than SRI's atomic
|
||||||
// unit would cause a 'price' of 0
|
// unit would cause a 'price' of 0
|
||||||
@@ -493,9 +494,9 @@ pub mod pallet {
|
|||||||
/// (the id of which is returned in the `Event::PoolCreated` event).
|
/// (the id of which is returned in the `Event::PoolCreated` event).
|
||||||
///
|
///
|
||||||
/// Once a pool is created, someone may [`Pallet::add_liquidity`] to it.
|
/// Once a pool is created, someone may [`Pallet::add_liquidity`] to it.
|
||||||
pub(crate) fn create_pool(coin: Coin) -> DispatchResult {
|
pub(crate) fn create_pool(coin: ExternalCoin) -> DispatchResult {
|
||||||
// get pool_id
|
// get pool_id
|
||||||
let pool_id = Self::get_pool_id(coin, Coin::Serai)?;
|
let pool_id = Self::get_pool_id(coin.into(), Coin::native())?;
|
||||||
ensure!(!Pools::<T>::contains_key(pool_id), Error::<T>::PoolExists);
|
ensure!(!Pools::<T>::contains_key(pool_id), Error::<T>::PoolExists);
|
||||||
|
|
||||||
let pool_account = Self::get_pool_account(pool_id);
|
let pool_account = Self::get_pool_account(pool_id);
|
||||||
@@ -508,9 +509,11 @@ pub mod pallet {
|
|||||||
|
|
||||||
/// A hook to be called whenever a network's session is rotated.
|
/// A hook to be called whenever a network's session is rotated.
|
||||||
pub fn on_new_session(network: NetworkId) {
|
pub fn on_new_session(network: NetworkId) {
|
||||||
// reset the oracle value
|
// Only track the price for non-SRI coins as this is SRI denominated
|
||||||
for coin in network.coins() {
|
if let NetworkId::External(n) = network {
|
||||||
SecurityOracleValue::<T>::set(*coin, Self::median_price(coin));
|
for coin in n.coins() {
|
||||||
|
SecurityOracleValue::<T>::set(coin, Self::median_price(coin));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -532,7 +535,7 @@ pub mod pallet {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn add_liquidity(
|
pub fn add_liquidity(
|
||||||
origin: OriginFor<T>,
|
origin: OriginFor<T>,
|
||||||
coin: Coin,
|
coin: ExternalCoin,
|
||||||
coin_desired: SubstrateAmount,
|
coin_desired: SubstrateAmount,
|
||||||
sri_desired: SubstrateAmount,
|
sri_desired: SubstrateAmount,
|
||||||
coin_min: SubstrateAmount,
|
coin_min: SubstrateAmount,
|
||||||
@@ -542,7 +545,7 @@ pub mod pallet {
|
|||||||
let sender = ensure_signed(origin)?;
|
let sender = ensure_signed(origin)?;
|
||||||
ensure!((sri_desired > 0) && (coin_desired > 0), Error::<T>::WrongDesiredAmount);
|
ensure!((sri_desired > 0) && (coin_desired > 0), Error::<T>::WrongDesiredAmount);
|
||||||
|
|
||||||
let pool_id = Self::get_pool_id(coin, Coin::Serai)?;
|
let pool_id = Self::get_pool_id(coin.into(), Coin::native())?;
|
||||||
|
|
||||||
// create the pool if it doesn't exist. We can just attempt to do that because our checks
|
// create the pool if it doesn't exist. We can just attempt to do that because our checks
|
||||||
// far enough to allow that.
|
// far enough to allow that.
|
||||||
@@ -552,7 +555,7 @@ pub mod pallet {
|
|||||||
let pool_account = Self::get_pool_account(pool_id);
|
let pool_account = Self::get_pool_account(pool_id);
|
||||||
|
|
||||||
let sri_reserve = Self::get_balance(&pool_account, Coin::Serai);
|
let sri_reserve = Self::get_balance(&pool_account, Coin::Serai);
|
||||||
let coin_reserve = Self::get_balance(&pool_account, coin);
|
let coin_reserve = Self::get_balance(&pool_account, coin.into());
|
||||||
|
|
||||||
let sri_amount: SubstrateAmount;
|
let sri_amount: SubstrateAmount;
|
||||||
let coin_amount: SubstrateAmount;
|
let coin_amount: SubstrateAmount;
|
||||||
@@ -583,16 +586,20 @@ pub mod pallet {
|
|||||||
&pool_account,
|
&pool_account,
|
||||||
Balance { coin: Coin::Serai, amount: Amount(sri_amount) },
|
Balance { coin: Coin::Serai, amount: Amount(sri_amount) },
|
||||||
)?;
|
)?;
|
||||||
Self::transfer(&sender, &pool_account, Balance { coin, amount: Amount(coin_amount) })?;
|
Self::transfer(
|
||||||
|
&sender,
|
||||||
|
&pool_account,
|
||||||
|
Balance { coin: coin.into(), amount: Amount(coin_amount) },
|
||||||
|
)?;
|
||||||
|
|
||||||
let total_supply = LiquidityTokens::<T>::supply(coin);
|
let total_supply = LiquidityTokens::<T>::supply(Coin::from(coin));
|
||||||
|
|
||||||
let lp_token_amount: SubstrateAmount;
|
let lp_token_amount: SubstrateAmount;
|
||||||
if total_supply == 0 {
|
if total_supply == 0 {
|
||||||
lp_token_amount = Self::calc_lp_amount_for_zero_supply(sri_amount, coin_amount)?;
|
lp_token_amount = Self::calc_lp_amount_for_zero_supply(sri_amount, coin_amount)?;
|
||||||
LiquidityTokens::<T>::mint(
|
LiquidityTokens::<T>::mint(
|
||||||
pool_account,
|
pool_account,
|
||||||
Balance { coin, amount: Amount(T::MintMinLiquidity::get()) },
|
Balance { coin: coin.into(), amount: Amount(T::MintMinLiquidity::get()) },
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
let side1 = Self::mul_div(sri_amount, total_supply, sri_reserve)?;
|
let side1 = Self::mul_div(sri_amount, total_supply, sri_reserve)?;
|
||||||
@@ -605,7 +612,10 @@ pub mod pallet {
|
|||||||
Error::<T>::InsufficientLiquidityMinted
|
Error::<T>::InsufficientLiquidityMinted
|
||||||
);
|
);
|
||||||
|
|
||||||
LiquidityTokens::<T>::mint(mint_to, Balance { coin, amount: Amount(lp_token_amount) })?;
|
LiquidityTokens::<T>::mint(
|
||||||
|
mint_to,
|
||||||
|
Balance { coin: coin.into(), amount: Amount(lp_token_amount) },
|
||||||
|
)?;
|
||||||
|
|
||||||
Self::deposit_event(Event::LiquidityAdded {
|
Self::deposit_event(Event::LiquidityAdded {
|
||||||
who: sender,
|
who: sender,
|
||||||
@@ -626,25 +636,24 @@ pub mod pallet {
|
|||||||
#[pallet::weight(T::WeightInfo::remove_liquidity())]
|
#[pallet::weight(T::WeightInfo::remove_liquidity())]
|
||||||
pub fn remove_liquidity(
|
pub fn remove_liquidity(
|
||||||
origin: OriginFor<T>,
|
origin: OriginFor<T>,
|
||||||
coin: Coin,
|
coin: ExternalCoin,
|
||||||
lp_token_burn: SubstrateAmount,
|
lp_token_burn: SubstrateAmount,
|
||||||
coin_min_receive: SubstrateAmount,
|
coin_min_receive: SubstrateAmount,
|
||||||
sri_min_receive: SubstrateAmount,
|
sri_min_receive: SubstrateAmount,
|
||||||
withdraw_to: T::AccountId,
|
withdraw_to: T::AccountId,
|
||||||
) -> DispatchResult {
|
) -> DispatchResult {
|
||||||
let sender = ensure_signed(origin.clone())?;
|
let sender = ensure_signed(origin.clone())?;
|
||||||
ensure!(coin != Coin::Serai, Error::<T>::EqualCoins);
|
|
||||||
|
|
||||||
let pool_id = Self::get_pool_id(coin, Coin::Serai).unwrap();
|
let pool_id = Self::get_pool_id(coin.into(), Coin::native()).unwrap();
|
||||||
ensure!(lp_token_burn > 0, Error::<T>::ZeroLiquidity);
|
ensure!(lp_token_burn > 0, Error::<T>::ZeroLiquidity);
|
||||||
|
|
||||||
Pools::<T>::get(pool_id).as_ref().ok_or(Error::<T>::PoolNotFound)?;
|
Pools::<T>::get(pool_id).as_ref().ok_or(Error::<T>::PoolNotFound)?;
|
||||||
|
|
||||||
let pool_account = Self::get_pool_account(pool_id);
|
let pool_account = Self::get_pool_account(pool_id);
|
||||||
let sri_reserve = Self::get_balance(&pool_account, Coin::Serai);
|
let sri_reserve = Self::get_balance(&pool_account, Coin::Serai);
|
||||||
let coin_reserve = Self::get_balance(&pool_account, coin);
|
let coin_reserve = Self::get_balance(&pool_account, coin.into());
|
||||||
|
|
||||||
let total_supply = LiquidityTokens::<T>::supply(coin);
|
let total_supply = LiquidityTokens::<T>::supply(Coin::from(coin));
|
||||||
let lp_redeem_amount = lp_token_burn;
|
let lp_redeem_amount = lp_token_burn;
|
||||||
|
|
||||||
let sri_amount = Self::mul_div(lp_redeem_amount, sri_reserve, total_supply)?;
|
let sri_amount = Self::mul_div(lp_redeem_amount, sri_reserve, total_supply)?;
|
||||||
@@ -665,14 +674,21 @@ pub mod pallet {
|
|||||||
ensure!(coin_reserve_left >= 1, Error::<T>::ReserveLeftLessThanMinimum);
|
ensure!(coin_reserve_left >= 1, Error::<T>::ReserveLeftLessThanMinimum);
|
||||||
|
|
||||||
// burn the provided lp token amount that includes the fee
|
// burn the provided lp token amount that includes the fee
|
||||||
LiquidityTokens::<T>::burn(origin, Balance { coin, amount: Amount(lp_token_burn) })?;
|
LiquidityTokens::<T>::burn(
|
||||||
|
origin,
|
||||||
|
Balance { coin: coin.into(), amount: Amount(lp_token_burn) },
|
||||||
|
)?;
|
||||||
|
|
||||||
Self::transfer(
|
Self::transfer(
|
||||||
&pool_account,
|
&pool_account,
|
||||||
&withdraw_to,
|
&withdraw_to,
|
||||||
Balance { coin: Coin::Serai, amount: Amount(sri_amount) },
|
Balance { coin: Coin::Serai, amount: Amount(sri_amount) },
|
||||||
)?;
|
)?;
|
||||||
Self::transfer(&pool_account, &withdraw_to, Balance { coin, amount: Amount(coin_amount) })?;
|
Self::transfer(
|
||||||
|
&pool_account,
|
||||||
|
&withdraw_to,
|
||||||
|
Balance { coin: coin.into(), amount: Amount(coin_amount) },
|
||||||
|
)?;
|
||||||
|
|
||||||
Self::deposit_event(Event::LiquidityRemoved {
|
Self::deposit_event(Event::LiquidityRemoved {
|
||||||
who: sender,
|
who: sender,
|
||||||
@@ -920,11 +936,9 @@ pub mod pallet {
|
|||||||
pub fn get_pool_id(coin1: Coin, coin2: Coin) -> Result<PoolId, Error<T>> {
|
pub fn get_pool_id(coin1: Coin, coin2: Coin) -> Result<PoolId, Error<T>> {
|
||||||
ensure!((coin1 == Coin::Serai) || (coin2 == Coin::Serai), Error::<T>::PoolNotFound);
|
ensure!((coin1 == Coin::Serai) || (coin2 == Coin::Serai), Error::<T>::PoolNotFound);
|
||||||
ensure!(coin1 != coin2, Error::<T>::EqualCoins);
|
ensure!(coin1 != coin2, Error::<T>::EqualCoins);
|
||||||
if coin1 == Coin::Serai {
|
ExternalCoin::try_from(coin1)
|
||||||
Ok(coin2)
|
.or_else(|()| ExternalCoin::try_from(coin2))
|
||||||
} else {
|
.map_err(|()| Error::<T>::PoolNotFound)
|
||||||
Ok(coin1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the balance of each coin in the pool.
|
/// Returns the balance of each coin in the pool.
|
||||||
|
|||||||
@@ -18,7 +18,10 @@
|
|||||||
// It has been forked into a crate distributed under the AGPL 3.0.
|
// It has been forked into a crate distributed under the AGPL 3.0.
|
||||||
// Please check the current distribution for up-to-date copyright and licensing information.
|
// Please check the current distribution for up-to-date copyright and licensing information.
|
||||||
|
|
||||||
use crate::{mock::*, *};
|
use crate::{
|
||||||
|
mock::{*, MEDIAN_PRICE_WINDOW_LENGTH},
|
||||||
|
*,
|
||||||
|
};
|
||||||
use frame_support::{assert_noop, assert_ok};
|
use frame_support::{assert_noop, assert_ok};
|
||||||
|
|
||||||
pub use coins_pallet as coins;
|
pub use coins_pallet as coins;
|
||||||
@@ -72,11 +75,13 @@ fn check_pool_accounts_dont_collide() {
|
|||||||
let mut map = HashSet::new();
|
let mut map = HashSet::new();
|
||||||
|
|
||||||
for coin in coins() {
|
for coin in coins() {
|
||||||
let account = Dex::get_pool_account(coin);
|
if let Coin::External(c) = coin {
|
||||||
if map.contains(&account) {
|
let account = Dex::get_pool_account(c);
|
||||||
panic!("Collision at {coin:?}");
|
if map.contains(&account) {
|
||||||
|
panic!("Collision at {c:?}");
|
||||||
|
}
|
||||||
|
map.insert(account);
|
||||||
}
|
}
|
||||||
map.insert(account);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,11 +103,11 @@ fn can_create_pool() {
|
|||||||
let coin_account_deposit: u64 = 0;
|
let coin_account_deposit: u64 = 0;
|
||||||
let user: PublicKey = system_address(b"user1").into();
|
let user: PublicKey = system_address(b"user1").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Monero;
|
let coin2 = Coin::External(ExternalCoin::Monero);
|
||||||
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||||
|
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(1000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(1000) }));
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
|
|
||||||
assert_eq!(balance(user, coin1), 1000 - coin_account_deposit);
|
assert_eq!(balance(user, coin1), 1000 - coin_account_deposit);
|
||||||
|
|
||||||
@@ -111,15 +116,13 @@ fn can_create_pool() {
|
|||||||
[Event::<Test>::PoolCreated { pool_id, pool_account: Dex::get_pool_account(pool_id) }]
|
[Event::<Test>::PoolCreated { pool_id, pool_account: Dex::get_pool_account(pool_id) }]
|
||||||
);
|
);
|
||||||
assert_eq!(pools(), vec![pool_id]);
|
assert_eq!(pools(), vec![pool_id]);
|
||||||
|
|
||||||
assert_noop!(Dex::create_pool(coin1), Error::<Test>::EqualCoins);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_same_pool_twice_should_fail() {
|
fn create_same_pool_twice_should_fail() {
|
||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let coin = Coin::Dai;
|
let coin = ExternalCoin::Dai;
|
||||||
assert_ok!(Dex::create_pool(coin));
|
assert_ok!(Dex::create_pool(coin));
|
||||||
assert_noop!(Dex::create_pool(coin), Error::<Test>::PoolExists);
|
assert_noop!(Dex::create_pool(coin), Error::<Test>::PoolExists);
|
||||||
});
|
});
|
||||||
@@ -129,13 +132,13 @@ fn create_same_pool_twice_should_fail() {
|
|||||||
fn different_pools_should_have_different_lp_tokens() {
|
fn different_pools_should_have_different_lp_tokens() {
|
||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Bitcoin;
|
let coin2 = Coin::External(ExternalCoin::Bitcoin);
|
||||||
let coin3 = Coin::Ether;
|
let coin3 = Coin::External(ExternalCoin::Ether);
|
||||||
let pool_id_1_2 = Dex::get_pool_id(coin1, coin2).unwrap();
|
let pool_id_1_2 = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||||
let pool_id_1_3 = Dex::get_pool_id(coin1, coin3).unwrap();
|
let pool_id_1_3 = Dex::get_pool_id(coin1, coin3).unwrap();
|
||||||
|
|
||||||
let lp_token2_1 = coin2;
|
let lp_token2_1 = coin2;
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
let lp_token3_1 = coin3;
|
let lp_token3_1 = coin3;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -146,7 +149,7 @@ fn different_pools_should_have_different_lp_tokens() {
|
|||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin3));
|
assert_ok!(Dex::create_pool(coin3.try_into().unwrap()));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
events(),
|
events(),
|
||||||
[Event::<Test>::PoolCreated {
|
[Event::<Test>::PoolCreated {
|
||||||
@@ -164,13 +167,13 @@ fn can_add_liquidity() {
|
|||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Dai;
|
let coin2 = Coin::External(ExternalCoin::Dai);
|
||||||
let coin3 = Coin::Monero;
|
let coin3 = Coin::External(ExternalCoin::Monero);
|
||||||
|
|
||||||
let lp_token1 = coin2;
|
let lp_token1 = coin2;
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
let lp_token2 = coin3;
|
let lp_token2 = coin3;
|
||||||
assert_ok!(Dex::create_pool(coin3));
|
assert_ok!(Dex::create_pool(coin3.try_into().unwrap()));
|
||||||
|
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(
|
assert_ok!(CoinsPallet::<Test>::mint(
|
||||||
user,
|
user,
|
||||||
@@ -179,7 +182,15 @@ fn can_add_liquidity() {
|
|||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin3, amount: Amount(1000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin3, amount: Amount(1000) }));
|
||||||
|
|
||||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 10, 10000, 10, 10000, user,));
|
assert_ok!(Dex::add_liquidity(
|
||||||
|
RuntimeOrigin::signed(user),
|
||||||
|
coin2.try_into().unwrap(),
|
||||||
|
10,
|
||||||
|
10000,
|
||||||
|
10,
|
||||||
|
10000,
|
||||||
|
user,
|
||||||
|
));
|
||||||
|
|
||||||
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||||
assert!(events().contains(&Event::<Test>::LiquidityAdded {
|
assert!(events().contains(&Event::<Test>::LiquidityAdded {
|
||||||
@@ -198,7 +209,15 @@ fn can_add_liquidity() {
|
|||||||
assert_eq!(pool_balance(user, lp_token1), 216);
|
assert_eq!(pool_balance(user, lp_token1), 216);
|
||||||
|
|
||||||
// try to pass the non-native - native coins, the result should be the same
|
// try to pass the non-native - native coins, the result should be the same
|
||||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin3, 10, 10000, 10, 10000, user,));
|
assert_ok!(Dex::add_liquidity(
|
||||||
|
RuntimeOrigin::signed(user),
|
||||||
|
coin3.try_into().unwrap(),
|
||||||
|
10,
|
||||||
|
10000,
|
||||||
|
10,
|
||||||
|
10000,
|
||||||
|
user,
|
||||||
|
));
|
||||||
|
|
||||||
let pool_id = Dex::get_pool_id(coin1, coin3).unwrap();
|
let pool_id = Dex::get_pool_id(coin1, coin3).unwrap();
|
||||||
assert!(events().contains(&Event::<Test>::LiquidityAdded {
|
assert!(events().contains(&Event::<Test>::LiquidityAdded {
|
||||||
@@ -223,12 +242,15 @@ fn add_tiny_liquidity_leads_to_insufficient_liquidity_minted_error() {
|
|||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Bitcoin;
|
let coin2 = ExternalCoin::Bitcoin;
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2));
|
||||||
|
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(1000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(1000) }));
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(
|
||||||
|
user,
|
||||||
|
Balance { coin: coin2.into(), amount: Amount(1000) }
|
||||||
|
));
|
||||||
|
|
||||||
assert_noop!(
|
assert_noop!(
|
||||||
Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 1, 1, 1, 1, user),
|
Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 1, 1, 1, 1, user),
|
||||||
@@ -242,11 +264,11 @@ fn add_tiny_liquidity_directly_to_pool_address() {
|
|||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Ether;
|
let coin2 = Coin::External(ExternalCoin::Ether);
|
||||||
let coin3 = Coin::Dai;
|
let coin3 = Coin::External(ExternalCoin::Dai);
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
assert_ok!(Dex::create_pool(coin3));
|
assert_ok!(Dex::create_pool(coin3.try_into().unwrap()));
|
||||||
|
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000 * 2) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000 * 2) }));
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(10000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(10000) }));
|
||||||
@@ -259,7 +281,15 @@ fn add_tiny_liquidity_directly_to_pool_address() {
|
|||||||
Balance { coin: coin1, amount: Amount(1000) }
|
Balance { coin: coin1, amount: Amount(1000) }
|
||||||
));
|
));
|
||||||
|
|
||||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 10, 10000, 10, 10000, user,));
|
assert_ok!(Dex::add_liquidity(
|
||||||
|
RuntimeOrigin::signed(user),
|
||||||
|
coin2.try_into().unwrap(),
|
||||||
|
10,
|
||||||
|
10000,
|
||||||
|
10,
|
||||||
|
10000,
|
||||||
|
user,
|
||||||
|
));
|
||||||
|
|
||||||
// check the same but for coin3 (non-native token)
|
// check the same but for coin3 (non-native token)
|
||||||
let pallet_account = Dex::get_pool_account(Dex::get_pool_id(coin1, coin3).unwrap());
|
let pallet_account = Dex::get_pool_account(Dex::get_pool_id(coin1, coin3).unwrap());
|
||||||
@@ -267,7 +297,15 @@ fn add_tiny_liquidity_directly_to_pool_address() {
|
|||||||
pallet_account,
|
pallet_account,
|
||||||
Balance { coin: coin2, amount: Amount(1) }
|
Balance { coin: coin2, amount: Amount(1) }
|
||||||
));
|
));
|
||||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin3, 10, 10000, 10, 10000, user,));
|
assert_ok!(Dex::add_liquidity(
|
||||||
|
RuntimeOrigin::signed(user),
|
||||||
|
coin3.try_into().unwrap(),
|
||||||
|
10,
|
||||||
|
10000,
|
||||||
|
10,
|
||||||
|
10000,
|
||||||
|
user,
|
||||||
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,11 +314,11 @@ fn can_remove_liquidity() {
|
|||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Monero;
|
let coin2 = Coin::External(ExternalCoin::Monero);
|
||||||
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||||
|
|
||||||
let lp_token = coin2;
|
let lp_token = coin2;
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
|
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(
|
assert_ok!(CoinsPallet::<Test>::mint(
|
||||||
user,
|
user,
|
||||||
@@ -290,7 +328,7 @@ fn can_remove_liquidity() {
|
|||||||
|
|
||||||
assert_ok!(Dex::add_liquidity(
|
assert_ok!(Dex::add_liquidity(
|
||||||
RuntimeOrigin::signed(user),
|
RuntimeOrigin::signed(user),
|
||||||
coin2,
|
coin2.try_into().unwrap(),
|
||||||
100000,
|
100000,
|
||||||
1000000000,
|
1000000000,
|
||||||
100000,
|
100000,
|
||||||
@@ -302,7 +340,7 @@ fn can_remove_liquidity() {
|
|||||||
|
|
||||||
assert_ok!(Dex::remove_liquidity(
|
assert_ok!(Dex::remove_liquidity(
|
||||||
RuntimeOrigin::signed(user),
|
RuntimeOrigin::signed(user),
|
||||||
coin2,
|
coin2.try_into().unwrap(),
|
||||||
total_lp_received,
|
total_lp_received,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@@ -334,15 +372,23 @@ fn can_not_redeem_more_lp_tokens_than_were_minted() {
|
|||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Dai;
|
let coin2 = Coin::External(ExternalCoin::Dai);
|
||||||
let lp_token = coin2;
|
let lp_token = coin2;
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
|
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||||
|
|
||||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 10, 10000, 10, 10000, user,));
|
assert_ok!(Dex::add_liquidity(
|
||||||
|
RuntimeOrigin::signed(user),
|
||||||
|
coin2.try_into().unwrap(),
|
||||||
|
10,
|
||||||
|
10000,
|
||||||
|
10,
|
||||||
|
10000,
|
||||||
|
user,
|
||||||
|
));
|
||||||
|
|
||||||
// Only 216 lp_tokens_minted
|
// Only 216 lp_tokens_minted
|
||||||
assert_eq!(pool_balance(user, lp_token), 216);
|
assert_eq!(pool_balance(user, lp_token), 216);
|
||||||
@@ -350,7 +396,7 @@ fn can_not_redeem_more_lp_tokens_than_were_minted() {
|
|||||||
assert_noop!(
|
assert_noop!(
|
||||||
Dex::remove_liquidity(
|
Dex::remove_liquidity(
|
||||||
RuntimeOrigin::signed(user),
|
RuntimeOrigin::signed(user),
|
||||||
coin2,
|
coin2.try_into().unwrap(),
|
||||||
216 + 1, // Try and redeem 10 lp tokens while only 9 minted.
|
216 + 1, // Try and redeem 10 lp tokens while only 9 minted.
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@@ -366,14 +412,22 @@ fn can_quote_price() {
|
|||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Ether;
|
let coin2 = Coin::External(ExternalCoin::Ether);
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
|
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(100000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(100000) }));
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||||
|
|
||||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 200, 10000, 1, 1, user,));
|
assert_ok!(Dex::add_liquidity(
|
||||||
|
RuntimeOrigin::signed(user),
|
||||||
|
coin2.try_into().unwrap(),
|
||||||
|
200,
|
||||||
|
10000,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
user,
|
||||||
|
));
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Dex::quote_price_exact_tokens_for_tokens(Coin::native(), coin2, 3000, false,),
|
Dex::quote_price_exact_tokens_for_tokens(Coin::native(), coin2, 3000, false,),
|
||||||
@@ -481,14 +535,22 @@ fn quote_price_exact_tokens_for_tokens_matches_execution() {
|
|||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let user2 = system_address(b"user2").into();
|
let user2 = system_address(b"user2").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Bitcoin;
|
let coin2 = Coin::External(ExternalCoin::Bitcoin);
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
|
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(100000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(100000) }));
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||||
|
|
||||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 200, 10000, 1, 1, user,));
|
assert_ok!(Dex::add_liquidity(
|
||||||
|
RuntimeOrigin::signed(user),
|
||||||
|
coin2.try_into().unwrap(),
|
||||||
|
200,
|
||||||
|
10000,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
user,
|
||||||
|
));
|
||||||
|
|
||||||
let amount = 1;
|
let amount = 1;
|
||||||
let quoted_price = 49;
|
let quoted_price = 49;
|
||||||
@@ -518,14 +580,22 @@ fn quote_price_tokens_for_exact_tokens_matches_execution() {
|
|||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let user2 = system_address(b"user2").into();
|
let user2 = system_address(b"user2").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Monero;
|
let coin2 = Coin::External(ExternalCoin::Monero);
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
|
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(100000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(100000) }));
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||||
|
|
||||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 200, 10000, 1, 1, user,));
|
assert_ok!(Dex::add_liquidity(
|
||||||
|
RuntimeOrigin::signed(user),
|
||||||
|
coin2.try_into().unwrap(),
|
||||||
|
200,
|
||||||
|
10000,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
user,
|
||||||
|
));
|
||||||
|
|
||||||
let amount = 49;
|
let amount = 49;
|
||||||
let quoted_price = 1;
|
let quoted_price = 1;
|
||||||
@@ -557,10 +627,10 @@ fn can_swap_with_native() {
|
|||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Ether;
|
let coin2 = Coin::External(ExternalCoin::Ether);
|
||||||
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
|
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||||
@@ -570,7 +640,7 @@ fn can_swap_with_native() {
|
|||||||
|
|
||||||
assert_ok!(Dex::add_liquidity(
|
assert_ok!(Dex::add_liquidity(
|
||||||
RuntimeOrigin::signed(user),
|
RuntimeOrigin::signed(user),
|
||||||
coin2,
|
coin2.try_into().unwrap(),
|
||||||
liquidity2,
|
liquidity2,
|
||||||
liquidity1,
|
liquidity1,
|
||||||
1,
|
1,
|
||||||
@@ -602,8 +672,8 @@ fn can_swap_with_realistic_values() {
|
|||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let sri = Coin::native();
|
let sri = Coin::native();
|
||||||
let dai = Coin::Dai;
|
let dai = Coin::External(ExternalCoin::Dai);
|
||||||
assert_ok!(Dex::create_pool(dai));
|
assert_ok!(Dex::create_pool(dai.try_into().unwrap()));
|
||||||
|
|
||||||
const UNIT: u64 = 1_000_000_000;
|
const UNIT: u64 = 1_000_000_000;
|
||||||
|
|
||||||
@@ -620,7 +690,7 @@ fn can_swap_with_realistic_values() {
|
|||||||
let liquidity_dai = 1_000_000 * UNIT;
|
let liquidity_dai = 1_000_000 * UNIT;
|
||||||
assert_ok!(Dex::add_liquidity(
|
assert_ok!(Dex::add_liquidity(
|
||||||
RuntimeOrigin::signed(user),
|
RuntimeOrigin::signed(user),
|
||||||
dai,
|
dai.try_into().unwrap(),
|
||||||
liquidity_dai,
|
liquidity_dai,
|
||||||
liquidity_sri,
|
liquidity_sri,
|
||||||
1,
|
1,
|
||||||
@@ -653,9 +723,9 @@ fn can_not_swap_in_pool_with_no_liquidity_added_yet() {
|
|||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Monero;
|
let coin2 = Coin::External(ExternalCoin::Monero);
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
|
|
||||||
// Check can't swap an empty pool
|
// Check can't swap an empty pool
|
||||||
assert_noop!(
|
assert_noop!(
|
||||||
@@ -676,11 +746,11 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() {
|
|||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Bitcoin;
|
let coin2 = Coin::External(ExternalCoin::Bitcoin);
|
||||||
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||||
let lp_token = coin2;
|
let lp_token = coin2;
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
|
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||||
@@ -690,7 +760,7 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() {
|
|||||||
|
|
||||||
assert_ok!(Dex::add_liquidity(
|
assert_ok!(Dex::add_liquidity(
|
||||||
RuntimeOrigin::signed(user),
|
RuntimeOrigin::signed(user),
|
||||||
coin2,
|
coin2.try_into().unwrap(),
|
||||||
liquidity2,
|
liquidity2,
|
||||||
liquidity1,
|
liquidity1,
|
||||||
1,
|
1,
|
||||||
@@ -714,7 +784,7 @@ fn check_no_panic_when_try_swap_close_to_empty_pool() {
|
|||||||
|
|
||||||
assert_ok!(Dex::remove_liquidity(
|
assert_ok!(Dex::remove_liquidity(
|
||||||
RuntimeOrigin::signed(user),
|
RuntimeOrigin::signed(user),
|
||||||
coin2,
|
coin2.try_into().unwrap(),
|
||||||
lp_token_minted,
|
lp_token_minted,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
@@ -787,9 +857,9 @@ fn swap_should_not_work_if_too_much_slippage() {
|
|||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Ether;
|
let coin2 = Coin::External(ExternalCoin::Ether);
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
|
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(10000) }));
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||||
@@ -799,7 +869,7 @@ fn swap_should_not_work_if_too_much_slippage() {
|
|||||||
|
|
||||||
assert_ok!(Dex::add_liquidity(
|
assert_ok!(Dex::add_liquidity(
|
||||||
RuntimeOrigin::signed(user),
|
RuntimeOrigin::signed(user),
|
||||||
coin2,
|
coin2.try_into().unwrap(),
|
||||||
liquidity2,
|
liquidity2,
|
||||||
liquidity1,
|
liquidity1,
|
||||||
1,
|
1,
|
||||||
@@ -827,10 +897,10 @@ fn can_swap_tokens_for_exact_tokens() {
|
|||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Dai;
|
let coin2 = Coin::External(ExternalCoin::Dai);
|
||||||
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
|
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(20000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(20000) }));
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||||
@@ -844,7 +914,7 @@ fn can_swap_tokens_for_exact_tokens() {
|
|||||||
|
|
||||||
assert_ok!(Dex::add_liquidity(
|
assert_ok!(Dex::add_liquidity(
|
||||||
RuntimeOrigin::signed(user),
|
RuntimeOrigin::signed(user),
|
||||||
coin2,
|
coin2.try_into().unwrap(),
|
||||||
liquidity2,
|
liquidity2,
|
||||||
liquidity1,
|
liquidity1,
|
||||||
1,
|
1,
|
||||||
@@ -882,11 +952,11 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() {
|
|||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let user2 = system_address(b"user2").into();
|
let user2 = system_address(b"user2").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Monero;
|
let coin2 = Coin::External(ExternalCoin::Monero);
|
||||||
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
let pool_id = Dex::get_pool_id(coin1, coin2).unwrap();
|
||||||
let lp_token = coin2;
|
let lp_token = coin2;
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
|
|
||||||
let base1 = 10000;
|
let base1 = 10000;
|
||||||
let base2 = 1000;
|
let base2 = 1000;
|
||||||
@@ -903,7 +973,7 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() {
|
|||||||
|
|
||||||
assert_ok!(Dex::add_liquidity(
|
assert_ok!(Dex::add_liquidity(
|
||||||
RuntimeOrigin::signed(user2),
|
RuntimeOrigin::signed(user2),
|
||||||
coin2,
|
coin2.try_into().unwrap(),
|
||||||
liquidity2,
|
liquidity2,
|
||||||
liquidity1,
|
liquidity1,
|
||||||
1,
|
1,
|
||||||
@@ -947,7 +1017,7 @@ fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() {
|
|||||||
|
|
||||||
assert_ok!(Dex::remove_liquidity(
|
assert_ok!(Dex::remove_liquidity(
|
||||||
RuntimeOrigin::signed(user2),
|
RuntimeOrigin::signed(user2),
|
||||||
coin2,
|
coin2.try_into().unwrap(),
|
||||||
lp_token_minted,
|
lp_token_minted,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
@@ -961,9 +1031,9 @@ fn swap_tokens_for_exact_tokens_should_not_work_if_too_much_slippage() {
|
|||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Ether;
|
let coin2 = Coin::External(ExternalCoin::Ether);
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
|
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(20000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(20000) }));
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin2, amount: Amount(1000) }));
|
||||||
@@ -973,7 +1043,7 @@ fn swap_tokens_for_exact_tokens_should_not_work_if_too_much_slippage() {
|
|||||||
|
|
||||||
assert_ok!(Dex::add_liquidity(
|
assert_ok!(Dex::add_liquidity(
|
||||||
RuntimeOrigin::signed(user),
|
RuntimeOrigin::signed(user),
|
||||||
coin2,
|
coin2.try_into().unwrap(),
|
||||||
liquidity2,
|
liquidity2,
|
||||||
liquidity1,
|
liquidity1,
|
||||||
1,
|
1,
|
||||||
@@ -1001,11 +1071,11 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() {
|
|||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Dai;
|
let coin2 = Coin::External(ExternalCoin::Dai);
|
||||||
let coin3 = Coin::Monero;
|
let coin3 = Coin::External(ExternalCoin::Monero);
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
assert_ok!(Dex::create_pool(coin3));
|
assert_ok!(Dex::create_pool(coin3.try_into().unwrap()));
|
||||||
|
|
||||||
let base1 = 10000;
|
let base1 = 10000;
|
||||||
let base2 = 10000;
|
let base2 = 10000;
|
||||||
@@ -1019,7 +1089,7 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() {
|
|||||||
|
|
||||||
assert_ok!(Dex::add_liquidity(
|
assert_ok!(Dex::add_liquidity(
|
||||||
RuntimeOrigin::signed(user),
|
RuntimeOrigin::signed(user),
|
||||||
coin2,
|
coin2.try_into().unwrap(),
|
||||||
liquidity2,
|
liquidity2,
|
||||||
liquidity1,
|
liquidity1,
|
||||||
1,
|
1,
|
||||||
@@ -1028,7 +1098,7 @@ fn swap_exact_tokens_for_tokens_in_multi_hops() {
|
|||||||
));
|
));
|
||||||
assert_ok!(Dex::add_liquidity(
|
assert_ok!(Dex::add_liquidity(
|
||||||
RuntimeOrigin::signed(user),
|
RuntimeOrigin::signed(user),
|
||||||
coin3,
|
coin3.try_into().unwrap(),
|
||||||
liquidity3,
|
liquidity3,
|
||||||
liquidity1,
|
liquidity1,
|
||||||
1,
|
1,
|
||||||
@@ -1089,11 +1159,11 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() {
|
|||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Bitcoin;
|
let coin2 = Coin::External(ExternalCoin::Bitcoin);
|
||||||
let coin3 = Coin::Ether;
|
let coin3 = Coin::External(ExternalCoin::Ether);
|
||||||
|
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
assert_ok!(Dex::create_pool(coin3));
|
assert_ok!(Dex::create_pool(coin3.try_into().unwrap()));
|
||||||
|
|
||||||
let base1 = 10000;
|
let base1 = 10000;
|
||||||
let base2 = 10000;
|
let base2 = 10000;
|
||||||
@@ -1107,7 +1177,7 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() {
|
|||||||
|
|
||||||
assert_ok!(Dex::add_liquidity(
|
assert_ok!(Dex::add_liquidity(
|
||||||
RuntimeOrigin::signed(user),
|
RuntimeOrigin::signed(user),
|
||||||
coin2,
|
coin2.try_into().unwrap(),
|
||||||
liquidity2,
|
liquidity2,
|
||||||
liquidity1,
|
liquidity1,
|
||||||
1,
|
1,
|
||||||
@@ -1116,7 +1186,7 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() {
|
|||||||
));
|
));
|
||||||
assert_ok!(Dex::add_liquidity(
|
assert_ok!(Dex::add_liquidity(
|
||||||
RuntimeOrigin::signed(user),
|
RuntimeOrigin::signed(user),
|
||||||
coin3,
|
coin3.try_into().unwrap(),
|
||||||
liquidity3,
|
liquidity3,
|
||||||
liquidity1,
|
liquidity1,
|
||||||
1,
|
1,
|
||||||
@@ -1154,7 +1224,7 @@ fn swap_tokens_for_exact_tokens_in_multi_hops() {
|
|||||||
fn can_not_swap_same_coin() {
|
fn can_not_swap_same_coin() {
|
||||||
new_test_ext().execute_with(|| {
|
new_test_ext().execute_with(|| {
|
||||||
let user = system_address(b"user1").into();
|
let user = system_address(b"user1").into();
|
||||||
let coin1 = Coin::Dai;
|
let coin1 = Coin::External(ExternalCoin::Dai);
|
||||||
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(1000) }));
|
assert_ok!(CoinsPallet::<Test>::mint(user, Balance { coin: coin1, amount: Amount(1000) }));
|
||||||
|
|
||||||
let exchange_amount = 10;
|
let exchange_amount = 10;
|
||||||
@@ -1188,10 +1258,10 @@ fn validate_pool_id_sorting() {
|
|||||||
// Serai < Bitcoin < Ether < Dai < Monero.
|
// Serai < Bitcoin < Ether < Dai < Monero.
|
||||||
// coin1 <= coin2 for this test to pass.
|
// coin1 <= coin2 for this test to pass.
|
||||||
let native = Coin::native();
|
let native = Coin::native();
|
||||||
let coin1 = Coin::Bitcoin;
|
let coin1 = Coin::External(ExternalCoin::Bitcoin);
|
||||||
let coin2 = Coin::Monero;
|
let coin2 = Coin::External(ExternalCoin::Monero);
|
||||||
assert_eq!(Dex::get_pool_id(native, coin2).unwrap(), coin2);
|
assert_eq!(Dex::get_pool_id(native, coin2).unwrap(), coin2.try_into().unwrap());
|
||||||
assert_eq!(Dex::get_pool_id(coin2, native).unwrap(), coin2);
|
assert_eq!(Dex::get_pool_id(coin2, native).unwrap(), coin2.try_into().unwrap());
|
||||||
assert!(matches!(Dex::get_pool_id(native, native), Err(Error::<Test>::EqualCoins)));
|
assert!(matches!(Dex::get_pool_id(native, native), Err(Error::<Test>::EqualCoins)));
|
||||||
assert!(matches!(Dex::get_pool_id(coin2, coin1), Err(Error::<Test>::PoolNotFound)));
|
assert!(matches!(Dex::get_pool_id(coin2, coin1), Err(Error::<Test>::PoolNotFound)));
|
||||||
assert!(coin2 > coin1);
|
assert!(coin2 > coin1);
|
||||||
@@ -1216,7 +1286,7 @@ fn cannot_block_pool_creation() {
|
|||||||
|
|
||||||
// The target pool the user wants to create is Native <=> Coin(2)
|
// The target pool the user wants to create is Native <=> Coin(2)
|
||||||
let coin1 = Coin::native();
|
let coin1 = Coin::native();
|
||||||
let coin2 = Coin::Ether;
|
let coin2 = Coin::External(ExternalCoin::Ether);
|
||||||
|
|
||||||
// Attacker computes the still non-existing pool account for the target pair
|
// Attacker computes the still non-existing pool account for the target pair
|
||||||
let pool_account = Dex::get_pool_account(Dex::get_pool_id(coin2, coin1).unwrap());
|
let pool_account = Dex::get_pool_account(Dex::get_pool_id(coin2, coin1).unwrap());
|
||||||
@@ -1238,7 +1308,7 @@ fn cannot_block_pool_creation() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// User can still create the pool
|
// User can still create the pool
|
||||||
assert_ok!(Dex::create_pool(coin2));
|
assert_ok!(Dex::create_pool(coin2.try_into().unwrap()));
|
||||||
|
|
||||||
// User has to transfer one Coin(2) token to the pool account (otherwise add_liquidity will
|
// User has to transfer one Coin(2) token to the pool account (otherwise add_liquidity will
|
||||||
// fail with `CoinTwoDepositDidNotMeetMinimum`), also transfer native token for the same error.
|
// fail with `CoinTwoDepositDidNotMeetMinimum`), also transfer native token for the same error.
|
||||||
@@ -1256,7 +1326,15 @@ fn cannot_block_pool_creation() {
|
|||||||
));
|
));
|
||||||
|
|
||||||
// add_liquidity shouldn't fail because of the number of consumers
|
// add_liquidity shouldn't fail because of the number of consumers
|
||||||
assert_ok!(Dex::add_liquidity(RuntimeOrigin::signed(user), coin2, 100, 9900, 10, 9900, user,));
|
assert_ok!(Dex::add_liquidity(
|
||||||
|
RuntimeOrigin::signed(user),
|
||||||
|
coin2.try_into().unwrap(),
|
||||||
|
100,
|
||||||
|
9900,
|
||||||
|
10,
|
||||||
|
9900,
|
||||||
|
user,
|
||||||
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1281,7 +1359,7 @@ fn test_median_price() {
|
|||||||
prices.push(OsRng.next_u64());
|
prices.push(OsRng.next_u64());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let coin = Coin::Bitcoin;
|
let coin = ExternalCoin::Bitcoin;
|
||||||
|
|
||||||
assert!(prices.len() >= (2 * usize::from(MEDIAN_PRICE_WINDOW_LENGTH)));
|
assert!(prices.len() >= (2 * usize::from(MEDIAN_PRICE_WINDOW_LENGTH)));
|
||||||
for i in 0 .. prices.len() {
|
for i in 0 .. prices.len() {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ pub mod pallet {
|
|||||||
#[pallet::event]
|
#[pallet::event]
|
||||||
#[pallet::generate_deposit(fn deposit_event)]
|
#[pallet::generate_deposit(fn deposit_event)]
|
||||||
pub enum Event<T: Config> {
|
pub enum Event<T: Config> {
|
||||||
EconomicSecurityReached { network: NetworkId },
|
EconomicSecurityReached { network: ExternalNetworkId },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pallet::pallet]
|
#[pallet::pallet]
|
||||||
@@ -33,17 +33,19 @@ pub mod pallet {
|
|||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
#[pallet::getter(fn economic_security_block)]
|
#[pallet::getter(fn economic_security_block)]
|
||||||
pub(crate) type EconomicSecurityBlock<T: Config> =
|
pub(crate) type EconomicSecurityBlock<T: Config> =
|
||||||
StorageMap<_, Identity, NetworkId, BlockNumberFor<T>, OptionQuery>;
|
StorageMap<_, Identity, ExternalNetworkId, BlockNumberFor<T>, OptionQuery>;
|
||||||
|
|
||||||
#[pallet::hooks]
|
#[pallet::hooks]
|
||||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||||
fn on_initialize(n: BlockNumberFor<T>) -> Weight {
|
fn on_initialize(n: BlockNumberFor<T>) -> Weight {
|
||||||
// we accept we reached economic security once we can mint smallest amount of a network's coin
|
// we accept we reached economic security once we can mint smallest amount of a network's coin
|
||||||
for coin in COINS {
|
for coin in EXTERNAL_COINS {
|
||||||
let existing = EconomicSecurityBlock::<T>::get(coin.network());
|
let existing = EconomicSecurityBlock::<T>::get(coin.network());
|
||||||
|
// TODO: we don't need to check for oracle value if is_allowed returns false when there is
|
||||||
|
// no coin value
|
||||||
if existing.is_none() &&
|
if existing.is_none() &&
|
||||||
Dex::<T>::security_oracle_value(coin).is_some() &&
|
Dex::<T>::security_oracle_value(coin).is_some() &&
|
||||||
<T as CoinsConfig>::AllowMint::is_allowed(&Balance { coin, amount: Amount(1) })
|
<T as CoinsConfig>::AllowMint::is_allowed(&ExternalBalance { coin, amount: Amount(1) })
|
||||||
{
|
{
|
||||||
EconomicSecurityBlock::<T>::set(coin.network(), Some(n));
|
EconomicSecurityBlock::<T>::set(coin.network(), Some(n));
|
||||||
Self::deposit_event(Event::EconomicSecurityReached { network: coin.network() });
|
Self::deposit_event(Event::EconomicSecurityReached { network: coin.network() });
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ pub mod pallet {
|
|||||||
pub type CurrentSession<T: Config> = StorageMap<_, Identity, NetworkId, u32, ValueQuery>;
|
pub type CurrentSession<T: Config> = StorageMap<_, Identity, NetworkId, u32, ValueQuery>;
|
||||||
|
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
pub(crate) type LastSwapVolume<T: Config> = StorageMap<_, Identity, Coin, u64, OptionQuery>;
|
pub(crate) type LastSwapVolume<T: Config> =
|
||||||
|
StorageMap<_, Identity, ExternalCoin, u64, OptionQuery>;
|
||||||
|
|
||||||
#[pallet::genesis_build]
|
#[pallet::genesis_build]
|
||||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||||
@@ -136,19 +137,16 @@ pub mod pallet {
|
|||||||
let mut total_distance: u64 = 0;
|
let mut total_distance: u64 = 0;
|
||||||
let reward_this_epoch = if pre_ec_security {
|
let reward_this_epoch = if pre_ec_security {
|
||||||
// calculate distance to economic security per network
|
// calculate distance to economic security per network
|
||||||
for n in NETWORKS {
|
for n in EXTERNAL_NETWORKS {
|
||||||
if n == NetworkId::Serai {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let required = ValidatorSets::<T>::required_stake_for_network(n);
|
let required = ValidatorSets::<T>::required_stake_for_network(n);
|
||||||
let mut current = ValidatorSets::<T>::total_allocated_stake(n).unwrap_or(Amount(0)).0;
|
let mut current =
|
||||||
|
ValidatorSets::<T>::total_allocated_stake(NetworkId::from(n)).unwrap_or(Amount(0)).0;
|
||||||
if current > required {
|
if current > required {
|
||||||
current = required;
|
current = required;
|
||||||
}
|
}
|
||||||
|
|
||||||
let distance = required - current;
|
let distance = required - current;
|
||||||
distances.insert(n, distance);
|
distances.insert(NetworkId::from(n), distance);
|
||||||
total_distance = total_distance.saturating_add(distance);
|
total_distance = total_distance.saturating_add(distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,9 +190,8 @@ pub mod pallet {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// get swap volumes
|
// get swap volumes
|
||||||
let mut volume_per_coin: BTreeMap<Coin, u64> = BTreeMap::new();
|
let mut volume_per_coin: BTreeMap<ExternalCoin, u64> = BTreeMap::new();
|
||||||
for c in COINS {
|
for c in EXTERNAL_COINS {
|
||||||
// this should return 0 for SRI and so it shouldn't affect the total volume.
|
|
||||||
let current_volume = Dex::<T>::swap_volume(c).unwrap_or(0);
|
let current_volume = Dex::<T>::swap_volume(c).unwrap_or(0);
|
||||||
let last_volume = LastSwapVolume::<T>::get(c).unwrap_or(0);
|
let last_volume = LastSwapVolume::<T>::get(c).unwrap_or(0);
|
||||||
let vol_this_epoch = current_volume.saturating_sub(last_volume);
|
let vol_this_epoch = current_volume.saturating_sub(last_volume);
|
||||||
@@ -209,11 +206,13 @@ pub mod pallet {
|
|||||||
let mut volume_per_network: BTreeMap<NetworkId, u64> = BTreeMap::new();
|
let mut volume_per_network: BTreeMap<NetworkId, u64> = BTreeMap::new();
|
||||||
for (c, vol) in &volume_per_coin {
|
for (c, vol) in &volume_per_coin {
|
||||||
volume_per_network.insert(
|
volume_per_network.insert(
|
||||||
c.network(),
|
c.network().into(),
|
||||||
(*volume_per_network.get(&c.network()).unwrap_or(&0)).saturating_add(*vol),
|
(*volume_per_network.get(&c.network().into()).unwrap_or(&0)).saturating_add(*vol),
|
||||||
);
|
);
|
||||||
total_volume = total_volume.saturating_add(*vol);
|
total_volume = total_volume.saturating_add(*vol);
|
||||||
}
|
}
|
||||||
|
// we add the serai network now
|
||||||
|
volume_per_network.insert(NetworkId::Serai, 0);
|
||||||
|
|
||||||
(
|
(
|
||||||
volume_per_network
|
volume_per_network
|
||||||
@@ -245,12 +244,13 @@ pub mod pallet {
|
|||||||
|
|
||||||
// distribute the rewards within the network
|
// distribute the rewards within the network
|
||||||
for (n, reward) in rewards_per_network {
|
for (n, reward) in rewards_per_network {
|
||||||
let (validators_reward, network_pool_reward) = if n == NetworkId::Serai {
|
let validators_reward = if let NetworkId::External(external_network) = n {
|
||||||
(reward, 0)
|
|
||||||
} else {
|
|
||||||
// calculate pool vs validator share
|
// calculate pool vs validator share
|
||||||
let capacity = ValidatorSets::<T>::total_allocated_stake(n).unwrap_or(Amount(0)).0;
|
let capacity =
|
||||||
let required = ValidatorSets::<T>::required_stake_for_network(n);
|
ValidatorSets::<T>::total_allocated_stake(NetworkId::from(external_network))
|
||||||
|
.unwrap_or(Amount(0))
|
||||||
|
.0;
|
||||||
|
let required = ValidatorSets::<T>::required_stake_for_network(external_network);
|
||||||
let unused_capacity = capacity.saturating_sub(required);
|
let unused_capacity = capacity.saturating_sub(required);
|
||||||
|
|
||||||
let distribution = unused_capacity.saturating_mul(ACCURACY_MULTIPLIER) / capacity;
|
let distribution = unused_capacity.saturating_mul(ACCURACY_MULTIPLIER) / capacity;
|
||||||
@@ -258,41 +258,44 @@ pub mod pallet {
|
|||||||
|
|
||||||
let validators_reward = DESIRED_DISTRIBUTION.saturating_mul(reward) / total;
|
let validators_reward = DESIRED_DISTRIBUTION.saturating_mul(reward) / total;
|
||||||
let network_pool_reward = reward.saturating_sub(validators_reward);
|
let network_pool_reward = reward.saturating_sub(validators_reward);
|
||||||
(validators_reward, network_pool_reward)
|
|
||||||
|
// send the rest to the pool
|
||||||
|
if network_pool_reward != 0 {
|
||||||
|
// these should be available to unwrap if we have a network_pool_reward. Because that
|
||||||
|
// means we had an unused capacity hence in a post-ec era.
|
||||||
|
let vpn = volume_per_network.as_ref().unwrap();
|
||||||
|
let vpc = volume_per_coin.as_ref().unwrap();
|
||||||
|
for c in external_network.coins() {
|
||||||
|
let pool_reward = u64::try_from(
|
||||||
|
u128::from(network_pool_reward).saturating_mul(u128::from(vpc[&c])) /
|
||||||
|
u128::from(vpn[&n]),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if Coins::<T>::mint(
|
||||||
|
Dex::<T>::get_pool_account(c),
|
||||||
|
Balance { coin: Coin::Serai, amount: Amount(pool_reward) },
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
// TODO: log the failure
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validators_reward
|
||||||
|
} else {
|
||||||
|
reward
|
||||||
};
|
};
|
||||||
|
|
||||||
// distribute validators rewards
|
// distribute validators rewards
|
||||||
Self::distribute_to_validators(n, validators_reward);
|
Self::distribute_to_validators(n, validators_reward);
|
||||||
|
|
||||||
// send the rest to the pool
|
|
||||||
if network_pool_reward != 0 {
|
|
||||||
// these should be available to unwrap if we have a network_pool_reward. Because that
|
|
||||||
// means we had an unused capacity hence in a post-ec era.
|
|
||||||
let vpn = volume_per_network.as_ref().unwrap();
|
|
||||||
let vpc = volume_per_coin.as_ref().unwrap();
|
|
||||||
for c in n.coins() {
|
|
||||||
let pool_reward = u64::try_from(
|
|
||||||
u128::from(network_pool_reward).saturating_mul(u128::from(vpc[c])) /
|
|
||||||
u128::from(vpn[&n]),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if Coins::<T>::mint(
|
|
||||||
Dex::<T>::get_pool_account(*c),
|
|
||||||
Balance { coin: Coin::Serai, amount: Amount(pool_reward) },
|
|
||||||
)
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
// TODO: log the failure
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we have the past session participants here in the emissions pallet so that we can
|
// TODO: we have the past session participants here in the emissions pallet so that we can
|
||||||
// distribute rewards to them in the next session. Ideally we should be able to fetch this
|
// distribute rewards to them in the next session. Ideally we should be able to fetch this
|
||||||
// information from valiadtor sets pallet.
|
// information from validator sets pallet.
|
||||||
Self::update_participants();
|
Self::update_participants();
|
||||||
Weight::zero() // TODO
|
Weight::zero() // TODO
|
||||||
}
|
}
|
||||||
@@ -318,11 +321,7 @@ pub mod pallet {
|
|||||||
|
|
||||||
/// Returns true if any of the external networks haven't reached economic security yet.
|
/// Returns true if any of the external networks haven't reached economic security yet.
|
||||||
fn pre_ec_security() -> bool {
|
fn pre_ec_security() -> bool {
|
||||||
for n in NETWORKS {
|
for n in EXTERNAL_NETWORKS {
|
||||||
if n == NetworkId::Serai {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if EconomicSecurity::<T>::economic_security_block(n).is_none() {
|
if EconomicSecurity::<T>::economic_security_block(n).is_none() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -362,16 +361,30 @@ pub mod pallet {
|
|||||||
pub fn swap_to_staked_sri(
|
pub fn swap_to_staked_sri(
|
||||||
to: PublicKey,
|
to: PublicKey,
|
||||||
network: NetworkId,
|
network: NetworkId,
|
||||||
balance: Balance,
|
balance: ExternalBalance,
|
||||||
) -> DispatchResult {
|
) -> DispatchResult {
|
||||||
// check the network didn't reach the economic security yet
|
// check the network didn't reach the economic security yet
|
||||||
if EconomicSecurity::<T>::economic_security_block(network).is_some() {
|
if let NetworkId::External(n) = network {
|
||||||
Err(Error::<T>::NetworkHasEconomicSecurity)?;
|
if EconomicSecurity::<T>::economic_security_block(n).is_some() {
|
||||||
|
Err(Error::<T>::NetworkHasEconomicSecurity)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// we target 20% of the network's stake to be behind the Serai network
|
||||||
|
let mut total_stake = 0;
|
||||||
|
for n in NETWORKS {
|
||||||
|
total_stake += ValidatorSets::<T>::total_allocated_stake(n).unwrap_or(Amount(0)).0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let stake = ValidatorSets::<T>::total_allocated_stake(network).unwrap_or(Amount(0)).0;
|
||||||
|
let desired_stake = total_stake / (100 / SERAI_VALIDATORS_DESIRED_PERCENTAGE);
|
||||||
|
if stake >= desired_stake {
|
||||||
|
Err(Error::<T>::NetworkHasEconomicSecurity)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// swap half of the liquidity for SRI to form PoL.
|
// swap half of the liquidity for SRI to form PoL.
|
||||||
let half = balance.amount.0 / 2;
|
let half = balance.amount.0 / 2;
|
||||||
let path = BoundedVec::try_from(vec![balance.coin, Coin::Serai]).unwrap();
|
let path = BoundedVec::try_from(vec![balance.coin.into(), Coin::Serai]).unwrap();
|
||||||
let origin = RawOrigin::Signed(POL_ACCOUNT.into());
|
let origin = RawOrigin::Signed(POL_ACCOUNT.into());
|
||||||
Dex::<T>::swap_exact_tokens_for_tokens(
|
Dex::<T>::swap_exact_tokens_for_tokens(
|
||||||
origin.clone().into(),
|
origin.clone().into(),
|
||||||
|
|||||||
@@ -54,9 +54,9 @@ pub mod pallet {
|
|||||||
#[pallet::event]
|
#[pallet::event]
|
||||||
#[pallet::generate_deposit(fn deposit_event)]
|
#[pallet::generate_deposit(fn deposit_event)]
|
||||||
pub enum Event<T: Config> {
|
pub enum Event<T: Config> {
|
||||||
GenesisLiquidityAdded { by: SeraiAddress, balance: Balance },
|
GenesisLiquidityAdded { by: SeraiAddress, balance: ExternalBalance },
|
||||||
GenesisLiquidityRemoved { by: SeraiAddress, balance: Balance },
|
GenesisLiquidityRemoved { by: SeraiAddress, balance: ExternalBalance },
|
||||||
GenesisLiquidityAddedToPool { coin1: Balance, sri: Amount },
|
GenesisLiquidityAddedToPool { coin: ExternalBalance, sri: Amount },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pallet::pallet]
|
#[pallet::pallet]
|
||||||
@@ -64,15 +64,23 @@ pub mod pallet {
|
|||||||
|
|
||||||
/// Keeps shares and the amount of coins per account.
|
/// Keeps shares and the amount of coins per account.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
pub(crate) type Liquidity<T: Config> =
|
pub(crate) type Liquidity<T: Config> = StorageDoubleMap<
|
||||||
StorageDoubleMap<_, Identity, Coin, Blake2_128Concat, PublicKey, LiquidityAmount, OptionQuery>;
|
_,
|
||||||
|
Identity,
|
||||||
|
ExternalCoin,
|
||||||
|
Blake2_128Concat,
|
||||||
|
PublicKey,
|
||||||
|
LiquidityAmount,
|
||||||
|
OptionQuery,
|
||||||
|
>;
|
||||||
|
|
||||||
/// Keeps the total shares and the total amount of coins per coin.
|
/// Keeps the total shares and the total amount of coins per coin.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
pub(crate) type Supply<T: Config> = StorageMap<_, Identity, Coin, LiquidityAmount, OptionQuery>;
|
pub(crate) type Supply<T: Config> =
|
||||||
|
StorageMap<_, Identity, ExternalCoin, LiquidityAmount, OptionQuery>;
|
||||||
|
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
pub(crate) type Oracle<T: Config> = StorageMap<_, Identity, Coin, u64, OptionQuery>;
|
pub(crate) type Oracle<T: Config> = StorageMap<_, Identity, ExternalCoin, u64, OptionQuery>;
|
||||||
|
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
#[pallet::getter(fn genesis_complete_block)]
|
#[pallet::getter(fn genesis_complete_block)]
|
||||||
@@ -102,11 +110,7 @@ pub mod pallet {
|
|||||||
// get pool & total values
|
// get pool & total values
|
||||||
let mut pool_values = vec![];
|
let mut pool_values = vec![];
|
||||||
let mut total_value: u128 = 0;
|
let mut total_value: u128 = 0;
|
||||||
for coin in COINS {
|
for coin in EXTERNAL_COINS {
|
||||||
if coin == Coin::Serai {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// initial coin value in terms of btc
|
// initial coin value in terms of btc
|
||||||
let Some(value) = Oracle::<T>::get(coin) else {
|
let Some(value) = Oracle::<T>::get(coin) else {
|
||||||
continue;
|
continue;
|
||||||
@@ -158,7 +162,7 @@ pub mod pallet {
|
|||||||
|
|
||||||
// let everyone know about the event
|
// let everyone know about the event
|
||||||
Self::deposit_event(Event::GenesisLiquidityAddedToPool {
|
Self::deposit_event(Event::GenesisLiquidityAddedToPool {
|
||||||
coin1: Balance { coin, amount: Amount(u64::try_from(pool_amount).unwrap()) },
|
coin: ExternalBalance { coin, amount: Amount(u64::try_from(pool_amount).unwrap()) },
|
||||||
sri: Amount(sri_amount),
|
sri: Amount(sri_amount),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -180,7 +184,7 @@ pub mod pallet {
|
|||||||
impl<T: Config> Pallet<T> {
|
impl<T: Config> Pallet<T> {
|
||||||
/// Add genesis liquidity for the given account. All accounts that provide liquidity
|
/// Add genesis liquidity for the given account. All accounts that provide liquidity
|
||||||
/// will receive the genesis SRI according to their liquidity ratio.
|
/// will receive the genesis SRI according to their liquidity ratio.
|
||||||
pub fn add_coin_liquidity(account: PublicKey, balance: Balance) -> DispatchResult {
|
pub fn add_coin_liquidity(account: PublicKey, balance: ExternalBalance) -> DispatchResult {
|
||||||
// check we are still in genesis period
|
// check we are still in genesis period
|
||||||
if Self::genesis_ended() {
|
if Self::genesis_ended() {
|
||||||
Err(Error::<T>::GenesisPeriodEnded)?;
|
Err(Error::<T>::GenesisPeriodEnded)?;
|
||||||
@@ -227,7 +231,7 @@ pub mod pallet {
|
|||||||
/// If networks is yet to be reached that threshold, None is returned.
|
/// If networks is yet to be reached that threshold, None is returned.
|
||||||
fn blocks_since_ec_security() -> Option<u64> {
|
fn blocks_since_ec_security() -> Option<u64> {
|
||||||
let mut min = u64::MAX;
|
let mut min = u64::MAX;
|
||||||
for n in NETWORKS {
|
for n in EXTERNAL_NETWORKS {
|
||||||
let ec_security_block =
|
let ec_security_block =
|
||||||
EconomicSecurity::<T>::economic_security_block(n)?.saturated_into::<u64>();
|
EconomicSecurity::<T>::economic_security_block(n)?.saturated_into::<u64>();
|
||||||
let current = <frame_system::Pallet<T>>::block_number().saturated_into::<u64>();
|
let current = <frame_system::Pallet<T>>::block_number().saturated_into::<u64>();
|
||||||
@@ -243,11 +247,7 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn oraclization_is_done() -> bool {
|
fn oraclization_is_done() -> bool {
|
||||||
for c in COINS {
|
for c in EXTERNAL_COINS {
|
||||||
if c == Coin::Serai {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if Oracle::<T>::get(c).is_none() {
|
if Oracle::<T>::get(c).is_none() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -276,7 +276,7 @@ pub mod pallet {
|
|||||||
/// Remove the provided genesis liquidity for an account.
|
/// Remove the provided genesis liquidity for an account.
|
||||||
#[pallet::call_index(0)]
|
#[pallet::call_index(0)]
|
||||||
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
||||||
pub fn remove_coin_liquidity(origin: OriginFor<T>, balance: Balance) -> DispatchResult {
|
pub fn remove_coin_liquidity(origin: OriginFor<T>, balance: ExternalBalance) -> DispatchResult {
|
||||||
let account = ensure_signed(origin)?;
|
let account = ensure_signed(origin)?;
|
||||||
let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into());
|
let origin = RawOrigin::Signed(GENESIS_LIQUIDITY_ACCOUNT.into());
|
||||||
let supply = Supply::<T>::get(balance.coin).ok_or(Error::<T>::NotEnoughLiquidity)?;
|
let supply = Supply::<T>::get(balance.coin).ok_or(Error::<T>::NotEnoughLiquidity)?;
|
||||||
@@ -297,7 +297,7 @@ pub mod pallet {
|
|||||||
|
|
||||||
// remove liquidity from pool
|
// remove liquidity from pool
|
||||||
let prev_sri = Coins::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai);
|
let prev_sri = Coins::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai);
|
||||||
let prev_coin = Coins::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), balance.coin);
|
let prev_coin = Coins::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), balance.coin.into());
|
||||||
Dex::<T>::remove_liquidity(
|
Dex::<T>::remove_liquidity(
|
||||||
origin.clone().into(),
|
origin.clone().into(),
|
||||||
balance.coin,
|
balance.coin,
|
||||||
@@ -307,7 +307,8 @@ pub mod pallet {
|
|||||||
GENESIS_LIQUIDITY_ACCOUNT.into(),
|
GENESIS_LIQUIDITY_ACCOUNT.into(),
|
||||||
)?;
|
)?;
|
||||||
let current_sri = Coins::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai);
|
let current_sri = Coins::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), Coin::Serai);
|
||||||
let current_coin = Coins::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), balance.coin);
|
let current_coin =
|
||||||
|
Coins::<T>::balance(GENESIS_LIQUIDITY_ACCOUNT.into(), balance.coin.into());
|
||||||
|
|
||||||
// burn the SRI if necessary
|
// burn the SRI if necessary
|
||||||
// TODO: take into consideration movement between pools.
|
// TODO: take into consideration movement between pools.
|
||||||
@@ -333,7 +334,7 @@ pub mod pallet {
|
|||||||
Coins::<T>::transfer(
|
Coins::<T>::transfer(
|
||||||
origin.clone().into(),
|
origin.clone().into(),
|
||||||
account,
|
account,
|
||||||
Balance { coin: balance.coin, amount: Amount(coin_out) },
|
Balance { coin: balance.coin.into(), amount: Amount(coin_out) },
|
||||||
)?;
|
)?;
|
||||||
Coins::<T>::transfer(
|
Coins::<T>::transfer(
|
||||||
origin.into(),
|
origin.into(),
|
||||||
@@ -366,7 +367,7 @@ pub mod pallet {
|
|||||||
Coins::<T>::transfer(
|
Coins::<T>::transfer(
|
||||||
origin.into(),
|
origin.into(),
|
||||||
account,
|
account,
|
||||||
Balance { coin: balance.coin, amount: Amount(existing.coins) },
|
Balance { coin: balance.coin.into(), amount: Amount(existing.coins) },
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
(
|
(
|
||||||
@@ -404,10 +405,10 @@ pub mod pallet {
|
|||||||
ensure_none(origin)?;
|
ensure_none(origin)?;
|
||||||
|
|
||||||
// set their relative values
|
// set their relative values
|
||||||
Oracle::<T>::set(Coin::Bitcoin, Some(10u64.pow(Coin::Bitcoin.decimals())));
|
Oracle::<T>::set(ExternalCoin::Bitcoin, Some(10u64.pow(ExternalCoin::Bitcoin.decimals())));
|
||||||
Oracle::<T>::set(Coin::Monero, Some(values.monero));
|
Oracle::<T>::set(ExternalCoin::Monero, Some(values.monero));
|
||||||
Oracle::<T>::set(Coin::Ether, Some(values.ether));
|
Oracle::<T>::set(ExternalCoin::Ether, Some(values.ether));
|
||||||
Oracle::<T>::set(Coin::Dai, Some(values.dai));
|
Oracle::<T>::set(ExternalCoin::Dai, Some(values.dai));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
use sp_io::hashing::blake2_256;
|
use sp_io::hashing::blake2_256;
|
||||||
|
|
||||||
use serai_primitives::{BlockHash, NetworkId};
|
use serai_primitives::*;
|
||||||
|
|
||||||
pub use in_instructions_primitives as primitives;
|
pub use in_instructions_primitives as primitives;
|
||||||
use primitives::*;
|
use primitives::*;
|
||||||
@@ -23,8 +23,6 @@ pub mod pallet {
|
|||||||
use sp_runtime::traits::Zero;
|
use sp_runtime::traits::Zero;
|
||||||
use sp_core::sr25519::Public;
|
use sp_core::sr25519::Public;
|
||||||
|
|
||||||
use serai_primitives::{Coin, Amount, Balance};
|
|
||||||
|
|
||||||
use frame_support::pallet_prelude::*;
|
use frame_support::pallet_prelude::*;
|
||||||
use frame_system::{pallet_prelude::*, RawOrigin};
|
use frame_system::{pallet_prelude::*, RawOrigin};
|
||||||
|
|
||||||
@@ -34,7 +32,7 @@ pub mod pallet {
|
|||||||
};
|
};
|
||||||
use dex_pallet::{Config as DexConfig, Pallet as Dex};
|
use dex_pallet::{Config as DexConfig, Pallet as Dex};
|
||||||
use validator_sets_pallet::{
|
use validator_sets_pallet::{
|
||||||
primitives::{Session, ValidatorSet},
|
primitives::{Session, ValidatorSet, ExternalValidatorSet},
|
||||||
Config as ValidatorSetsConfig, Pallet as ValidatorSets,
|
Config as ValidatorSetsConfig, Pallet as ValidatorSets,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -61,7 +59,7 @@ pub mod pallet {
|
|||||||
#[pallet::generate_deposit(fn deposit_event)]
|
#[pallet::generate_deposit(fn deposit_event)]
|
||||||
pub enum Event<T: Config> {
|
pub enum Event<T: Config> {
|
||||||
Batch {
|
Batch {
|
||||||
network: NetworkId,
|
network: ExternalNetworkId,
|
||||||
publishing_session: Session,
|
publishing_session: Session,
|
||||||
id: u32,
|
id: u32,
|
||||||
external_network_block_hash: BlockHash,
|
external_network_block_hash: BlockHash,
|
||||||
@@ -85,17 +83,18 @@ pub mod pallet {
|
|||||||
// The ID of the last executed Batch for a network.
|
// The ID of the last executed Batch for a network.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
#[pallet::getter(fn batches)]
|
#[pallet::getter(fn batches)]
|
||||||
pub(crate) type LastBatch<T: Config> = StorageMap<_, Identity, NetworkId, u32, OptionQuery>;
|
pub(crate) type LastBatch<T: Config> =
|
||||||
|
StorageMap<_, Identity, ExternalNetworkId, u32, OptionQuery>;
|
||||||
|
|
||||||
// The last Serai block in which this validator set included a batch
|
// The last Serai block in which this validator set included a batch
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
#[pallet::getter(fn last_batch_block)]
|
#[pallet::getter(fn last_batch_block)]
|
||||||
pub(crate) type LastBatchBlock<T: Config> =
|
pub(crate) type LastBatchBlock<T: Config> =
|
||||||
StorageMap<_, Identity, NetworkId, BlockNumberFor<T>, OptionQuery>;
|
StorageMap<_, Identity, ExternalNetworkId, BlockNumberFor<T>, OptionQuery>;
|
||||||
|
|
||||||
// Halted networks.
|
// Halted networks.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
pub(crate) type Halted<T: Config> = StorageMap<_, Identity, NetworkId, (), OptionQuery>;
|
pub(crate) type Halted<T: Config> = StorageMap<_, Identity, ExternalNetworkId, (), OptionQuery>;
|
||||||
|
|
||||||
impl<T: Config> Pallet<T> {
|
impl<T: Config> Pallet<T> {
|
||||||
// Use a dedicated transaction layer when executing this InInstruction
|
// Use a dedicated transaction layer when executing this InInstruction
|
||||||
@@ -104,8 +103,7 @@ pub mod pallet {
|
|||||||
fn execute(instruction: &InInstructionWithBalance) -> Result<(), DispatchError> {
|
fn execute(instruction: &InInstructionWithBalance) -> Result<(), DispatchError> {
|
||||||
match &instruction.instruction {
|
match &instruction.instruction {
|
||||||
InInstruction::Transfer(address) => {
|
InInstruction::Transfer(address) => {
|
||||||
let address = *address;
|
Coins::<T>::mint(address.into(), instruction.balance.into())?;
|
||||||
Coins::<T>::mint(address.into(), instruction.balance)?;
|
|
||||||
}
|
}
|
||||||
InInstruction::Dex(call) => {
|
InInstruction::Dex(call) => {
|
||||||
// This will only be initiated by external chain transactions. That is why we only need
|
// This will only be initiated by external chain transactions. That is why we only need
|
||||||
@@ -118,11 +116,11 @@ pub mod pallet {
|
|||||||
let coin = instruction.balance.coin;
|
let coin = instruction.balance.coin;
|
||||||
|
|
||||||
// mint the given coin on the account
|
// mint the given coin on the account
|
||||||
Coins::<T>::mint(IN_INSTRUCTION_EXECUTOR.into(), instruction.balance)?;
|
Coins::<T>::mint(IN_INSTRUCTION_EXECUTOR.into(), instruction.balance.into())?;
|
||||||
|
|
||||||
// swap half of it for SRI
|
// swap half of it for SRI
|
||||||
let half = instruction.balance.amount.0 / 2;
|
let half = instruction.balance.amount.0 / 2;
|
||||||
let path = BoundedVec::try_from(vec![coin, Coin::Serai]).unwrap();
|
let path = BoundedVec::try_from(vec![coin.into(), Coin::Serai]).unwrap();
|
||||||
Dex::<T>::swap_exact_tokens_for_tokens(
|
Dex::<T>::swap_exact_tokens_for_tokens(
|
||||||
origin.clone().into(),
|
origin.clone().into(),
|
||||||
path,
|
path,
|
||||||
@@ -148,13 +146,13 @@ pub mod pallet {
|
|||||||
// TODO: minimums are set to 1 above to guarantee successful adding liq call.
|
// TODO: minimums are set to 1 above to guarantee successful adding liq call.
|
||||||
// Ideally we either get this info from user or send the leftovers back to user.
|
// Ideally we either get this info from user or send the leftovers back to user.
|
||||||
// Let's send the leftovers back to user for now.
|
// Let's send the leftovers back to user for now.
|
||||||
let coin_balance = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), coin);
|
let coin_balance = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), coin.into());
|
||||||
let sri_balance = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), Coin::Serai);
|
let sri_balance = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), Coin::Serai);
|
||||||
if coin_balance != Amount(0) {
|
if coin_balance != Amount(0) {
|
||||||
Coins::<T>::transfer_internal(
|
Coins::<T>::transfer_internal(
|
||||||
IN_INSTRUCTION_EXECUTOR.into(),
|
IN_INSTRUCTION_EXECUTOR.into(),
|
||||||
address.into(),
|
address.into(),
|
||||||
Balance { coin, amount: coin_balance },
|
Balance { coin: coin.into(), amount: coin_balance },
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
if sri_balance != Amount(0) {
|
if sri_balance != Amount(0) {
|
||||||
@@ -175,10 +173,10 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mint the given coin on our account
|
// mint the given coin on our account
|
||||||
Coins::<T>::mint(IN_INSTRUCTION_EXECUTOR.into(), instruction.balance)?;
|
Coins::<T>::mint(IN_INSTRUCTION_EXECUTOR.into(), instruction.balance.into())?;
|
||||||
|
|
||||||
// get the path
|
// get the path
|
||||||
let mut path = vec![instruction.balance.coin, Coin::Serai];
|
let mut path = vec![instruction.balance.coin.into(), Coin::Serai];
|
||||||
if !native_coin {
|
if !native_coin {
|
||||||
path.push(out_balance.coin);
|
path.push(out_balance.coin);
|
||||||
}
|
}
|
||||||
@@ -212,7 +210,10 @@ pub mod pallet {
|
|||||||
instruction: OutInstruction {
|
instruction: OutInstruction {
|
||||||
address: out_address.clone().as_external().unwrap(),
|
address: out_address.clone().as_external().unwrap(),
|
||||||
},
|
},
|
||||||
balance: Balance { coin: out_balance.coin, amount: coin_balance },
|
balance: ExternalBalance {
|
||||||
|
coin: out_balance.coin.try_into().unwrap(),
|
||||||
|
amount: coin_balance,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
Coins::<T>::burn_with_instruction(origin.into(), instruction)?;
|
Coins::<T>::burn_with_instruction(origin.into(), instruction)?;
|
||||||
}
|
}
|
||||||
@@ -220,20 +221,18 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
InInstruction::GenesisLiquidity(address) => {
|
InInstruction::GenesisLiquidity(address) => {
|
||||||
let address = *address;
|
Coins::<T>::mint(GENESIS_LIQUIDITY_ACCOUNT.into(), instruction.balance.into())?;
|
||||||
Coins::<T>::mint(GENESIS_LIQUIDITY_ACCOUNT.into(), instruction.balance)?;
|
|
||||||
GenesisLiq::<T>::add_coin_liquidity(address.into(), instruction.balance)?;
|
GenesisLiq::<T>::add_coin_liquidity(address.into(), instruction.balance)?;
|
||||||
}
|
}
|
||||||
InInstruction::SwapToStakedSRI(address, network) => {
|
InInstruction::SwapToStakedSRI(address, network) => {
|
||||||
let address = *address;
|
Coins::<T>::mint(POL_ACCOUNT.into(), instruction.balance.into())?;
|
||||||
Coins::<T>::mint(POL_ACCOUNT.into(), instruction.balance)?;
|
Emissions::<T>::swap_to_staked_sri(address.into(), network, instruction.balance)?;
|
||||||
Emissions::<T>::swap_to_staked_sri(address.into(), *network, instruction.balance)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn halt(network: NetworkId) -> Result<(), DispatchError> {
|
pub fn halt(network: ExternalNetworkId) -> Result<(), DispatchError> {
|
||||||
Halted::<T>::set(network, Some(()));
|
Halted::<T>::set(network, Some(()));
|
||||||
Self::deposit_event(Event::Halt { network });
|
Self::deposit_event(Event::Halt { network });
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -241,13 +240,13 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn keys_for_network<T: Config>(
|
fn keys_for_network<T: Config>(
|
||||||
network: NetworkId,
|
network: ExternalNetworkId,
|
||||||
) -> Result<(Session, Option<Public>, Option<Public>), InvalidTransaction> {
|
) -> Result<(Session, Option<Public>, Option<Public>), InvalidTransaction> {
|
||||||
// If there's no session set, and therefore no keys set, then this must be an invalid signature
|
// If there's no session set, and therefore no keys set, then this must be an invalid signature
|
||||||
let Some(session) = ValidatorSets::<T>::session(network) else {
|
let Some(session) = ValidatorSets::<T>::session(NetworkId::from(network)) else {
|
||||||
Err(InvalidTransaction::BadProof)?
|
Err(InvalidTransaction::BadProof)?
|
||||||
};
|
};
|
||||||
let mut set = ValidatorSet { session, network };
|
let mut set = ExternalValidatorSet { network, session };
|
||||||
let latest = ValidatorSets::<T>::keys(set).map(|keys| keys.0);
|
let latest = ValidatorSets::<T>::keys(set).map(|keys| keys.0);
|
||||||
let prior = if set.session.0 != 0 {
|
let prior = if set.session.0 != 0 {
|
||||||
set.session.0 -= 1;
|
set.session.0 -= 1;
|
||||||
@@ -290,12 +289,7 @@ pub mod pallet {
|
|||||||
if batch.batch.encode().len() > MAX_BATCH_SIZE {
|
if batch.batch.encode().len() > MAX_BATCH_SIZE {
|
||||||
Err(InvalidTransaction::ExhaustsResources)?;
|
Err(InvalidTransaction::ExhaustsResources)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let network = batch.batch.network;
|
let network = batch.batch.network;
|
||||||
// Don't allow the Serai set to publish `Batch`s as-if Serai itself was an external network
|
|
||||||
if network == NetworkId::Serai {
|
|
||||||
Err(InvalidTransaction::Custom(0))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify the signature
|
// verify the signature
|
||||||
let (current_session, prior, current) = keys_for_network::<T>(network)?;
|
let (current_session, prior, current) = keys_for_network::<T>(network)?;
|
||||||
@@ -325,7 +319,7 @@ pub mod pallet {
|
|||||||
// key is publishing `Batch`s. This should only happen once the current key has verified all
|
// key is publishing `Batch`s. This should only happen once the current key has verified all
|
||||||
// `Batch`s published by the prior key, meaning they are accepting the hand-over.
|
// `Batch`s published by the prior key, meaning they are accepting the hand-over.
|
||||||
if prior.is_some() && (!valid_by_prior) {
|
if prior.is_some() && (!valid_by_prior) {
|
||||||
ValidatorSets::<T>::retire_set(ValidatorSet { network, session: prior_session });
|
ValidatorSets::<T>::retire_set(ValidatorSet { network: network.into(), session: prior_session });
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that this validator set isn't publishing a batch more than once per block
|
// check that this validator set isn't publishing a batch more than once per block
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user