mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-23 11:15:16 +00:00
Add Dex pallet (#407)
* Move pallet-asset-conversion * update licensing * initial integration * Integrate Currency & Assets types * integrate liquidity tokens * fmt * integrate dex pallet tests * fmt * compilation error fixes * integrate dex benchmarks * fmt * cargo clippy * replace all occurrences of "asset" with "coin" * add the actual add liq/swap logic to in-instructions * add client side & tests * fix deny * Lint and changes - Renames InInstruction::AddLiquidity to InInstruction::SwapAndAddLiquidity - Makes create_pool an internal function - Makes dex-pallet exclusively create pools against a native coin - Removes various fees - Adds new crates to GH workflow * Fix rebase artifacts * Correct other rebase artifact * Correct CI specification for liquidity-tokens * Correct primitives' test to the standardized pallet account scheme --------- Co-authored-by: Luke Parker <lukeparker5132@gmail.com>
This commit is contained in:
parent
facb5817c4
commit
899a9604e1
38 changed files with 5327 additions and 27 deletions
3
.github/workflows/tests.yml
vendored
3
.github/workflows/tests.yml
vendored
|
@ -62,6 +62,9 @@ jobs:
|
||||||
-p serai-primitives \
|
-p serai-primitives \
|
||||||
-p serai-coins-primitives \
|
-p serai-coins-primitives \
|
||||||
-p serai-coins-pallet \
|
-p serai-coins-pallet \
|
||||||
|
-p serai-liquidity-tokens-pallet \
|
||||||
|
-p serai-dex-primitives \
|
||||||
|
-p serai-dex-pallet \
|
||||||
-p serai-validator-sets-primitives \
|
-p serai-validator-sets-primitives \
|
||||||
-p serai-validator-sets-pallet \
|
-p serai-validator-sets-pallet \
|
||||||
-p serai-in-instructions-primitives \
|
-p serai-in-instructions-primitives \
|
||||||
|
|
54
Cargo.lock
generated
54
Cargo.lock
generated
|
@ -8179,6 +8179,7 @@ dependencies = [
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
"scale-info",
|
"scale-info",
|
||||||
"serai-coins-primitives",
|
"serai-coins-primitives",
|
||||||
|
"serai-dex-primitives",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"sp-core",
|
"sp-core",
|
||||||
"sp-runtime",
|
"sp-runtime",
|
||||||
|
@ -8259,6 +8260,40 @@ dependencies = [
|
||||||
"rocksdb",
|
"rocksdb",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serai-dex-pallet"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"frame-benchmarking",
|
||||||
|
"frame-support",
|
||||||
|
"frame-system",
|
||||||
|
"parity-scale-codec",
|
||||||
|
"scale-info",
|
||||||
|
"serai-coins-pallet",
|
||||||
|
"serai-dex-primitives",
|
||||||
|
"serai-liquidity-tokens-pallet",
|
||||||
|
"serai-primitives",
|
||||||
|
"sp-api",
|
||||||
|
"sp-arithmetic",
|
||||||
|
"sp-core",
|
||||||
|
"sp-io",
|
||||||
|
"sp-runtime",
|
||||||
|
"sp-std",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serai-dex-primitives"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"frame-benchmarking",
|
||||||
|
"frame-support",
|
||||||
|
"parity-scale-codec",
|
||||||
|
"scale-info",
|
||||||
|
"serai-primitives",
|
||||||
|
"sp-runtime",
|
||||||
|
"sp-std",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serai-docker-tests"
|
name = "serai-docker-tests"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -8301,6 +8336,7 @@ dependencies = [
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
"scale-info",
|
"scale-info",
|
||||||
"serai-coins-pallet",
|
"serai-coins-pallet",
|
||||||
|
"serai-dex-pallet",
|
||||||
"serai-in-instructions-primitives",
|
"serai-in-instructions-primitives",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serai-validator-sets-pallet",
|
"serai-validator-sets-pallet",
|
||||||
|
@ -8308,6 +8344,7 @@ dependencies = [
|
||||||
"sp-core",
|
"sp-core",
|
||||||
"sp-io",
|
"sp-io",
|
||||||
"sp-runtime",
|
"sp-runtime",
|
||||||
|
"sp-std",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -8326,6 +8363,20 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serai-liquidity-tokens-pallet"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"frame-support",
|
||||||
|
"frame-system",
|
||||||
|
"parity-scale-codec",
|
||||||
|
"scale-info",
|
||||||
|
"serai-dex-primitives",
|
||||||
|
"serai-primitives",
|
||||||
|
"sp-core",
|
||||||
|
"sp-std",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serai-message-queue"
|
name = "serai-message-queue"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -8543,7 +8594,10 @@ dependencies = [
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
"scale-info",
|
"scale-info",
|
||||||
"serai-coins-pallet",
|
"serai-coins-pallet",
|
||||||
|
"serai-dex-pallet",
|
||||||
|
"serai-dex-primitives",
|
||||||
"serai-in-instructions-pallet",
|
"serai-in-instructions-pallet",
|
||||||
|
"serai-liquidity-tokens-pallet",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serai-signals-pallet",
|
"serai-signals-pallet",
|
||||||
"serai-validator-sets-pallet",
|
"serai-validator-sets-pallet",
|
||||||
|
|
|
@ -55,6 +55,9 @@ exceptions = [
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-coordinator" },
|
{ allow = ["AGPL-3.0"], name = "serai-coordinator" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-coins-pallet" },
|
{ allow = ["AGPL-3.0"], name = "serai-coins-pallet" },
|
||||||
|
{ allow = ["AGPL-3.0"], name = "serai-liquidity-tokens-pallet" },
|
||||||
|
{ allow = ["AGPL-3.0"], name = "serai-dex-primitives" },
|
||||||
|
{ allow = ["AGPL-3.0"], name = "serai-dex-pallet" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-in-instructions-pallet" },
|
{ allow = ["AGPL-3.0"], name = "serai-in-instructions-pallet" },
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ async fn test_substrate_signer() {
|
||||||
balance: Balance { coin: Coin::Bitcoin, amount: Amount(1000) },
|
balance: Balance { coin: Coin::Bitcoin, amount: Amount(1000) },
|
||||||
},
|
},
|
||||||
InInstructionWithBalance {
|
InInstructionWithBalance {
|
||||||
instruction: InInstruction::Dex(Data::new(vec![0xcc; 128]).unwrap()),
|
instruction: InInstruction::Dex(DexCall::SwapAndAddLiquidity(SeraiAddress([0xbb; 32]))),
|
||||||
balance: Balance { coin: Coin::Monero, amount: Amount(9999999999999999) },
|
balance: Balance { coin: Coin::Monero, amount: Amount(9999999999999999) },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
71
substrate/client/src/serai/dex.rs
Normal file
71
substrate/client/src/serai/dex.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
use sp_core::bounded_vec::BoundedVec;
|
||||||
|
use serai_runtime::{
|
||||||
|
primitives::{SeraiAddress, Amount, Coin},
|
||||||
|
dex, Dex, Runtime,
|
||||||
|
};
|
||||||
|
|
||||||
|
use subxt::tx::Payload;
|
||||||
|
|
||||||
|
use crate::{SeraiError, Composite, TemporalSerai, scale_composite};
|
||||||
|
|
||||||
|
const PALLET: &str = "Dex";
|
||||||
|
|
||||||
|
pub type DexEvent = dex::Event<Runtime>;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct SeraiDex<'a>(pub(crate) TemporalSerai<'a>);
|
||||||
|
impl<'a> SeraiDex<'a> {
|
||||||
|
pub async fn all_events(&self) -> Result<Vec<DexEvent>, SeraiError> {
|
||||||
|
self.0.events::<Dex, _>(|_| true).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_liquidity(
|
||||||
|
coin: Coin,
|
||||||
|
coin_amount: Amount,
|
||||||
|
sri_amount: Amount,
|
||||||
|
min_coin_amount: Amount,
|
||||||
|
min_sri_amount: Amount,
|
||||||
|
address: SeraiAddress,
|
||||||
|
) -> Payload<Composite<()>> {
|
||||||
|
Payload::new(
|
||||||
|
PALLET,
|
||||||
|
"add_liquidity",
|
||||||
|
scale_composite(dex::Call::<Runtime>::add_liquidity {
|
||||||
|
coin1: coin,
|
||||||
|
coin2: Coin::Serai,
|
||||||
|
amount1_desired: coin_amount.0,
|
||||||
|
amount2_desired: sri_amount.0,
|
||||||
|
amount1_min: min_coin_amount.0,
|
||||||
|
amount2_min: min_sri_amount.0,
|
||||||
|
mint_to: address.into(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn swap(
|
||||||
|
from_coin: Coin,
|
||||||
|
to_coin: Coin,
|
||||||
|
amount_in: Amount,
|
||||||
|
amount_out_min: Amount,
|
||||||
|
address: SeraiAddress,
|
||||||
|
) -> Payload<Composite<()>> {
|
||||||
|
let path = if to_coin.is_native() {
|
||||||
|
BoundedVec::try_from(vec![from_coin, Coin::Serai]).unwrap()
|
||||||
|
} else if from_coin.is_native() {
|
||||||
|
BoundedVec::try_from(vec![Coin::Serai, to_coin]).unwrap()
|
||||||
|
} else {
|
||||||
|
BoundedVec::try_from(vec![from_coin, Coin::Serai, to_coin]).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
Payload::new(
|
||||||
|
PALLET,
|
||||||
|
"swap_exact_tokens_for_tokens",
|
||||||
|
scale_composite(dex::Call::<Runtime>::swap_exact_tokens_for_tokens {
|
||||||
|
path,
|
||||||
|
amount_in: amount_in.0,
|
||||||
|
amount_out_min: amount_out_min.0,
|
||||||
|
send_to: address.into(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -35,6 +35,8 @@ use serai_runtime::{
|
||||||
|
|
||||||
pub mod coins;
|
pub mod coins;
|
||||||
pub use coins::SeraiCoins;
|
pub use coins::SeraiCoins;
|
||||||
|
pub mod dex;
|
||||||
|
pub use dex::SeraiDex;
|
||||||
pub mod in_instructions;
|
pub mod in_instructions;
|
||||||
pub use in_instructions::SeraiInInstructions;
|
pub use in_instructions::SeraiInInstructions;
|
||||||
pub mod validator_sets;
|
pub mod validator_sets;
|
||||||
|
@ -347,6 +349,10 @@ impl<'a> TemporalSerai<'a> {
|
||||||
SeraiCoins(self)
|
SeraiCoins(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dex(self) -> SeraiDex<'a> {
|
||||||
|
SeraiDex(self)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn in_instructions(self) -> SeraiInInstructions<'a> {
|
pub fn in_instructions(self) -> SeraiInInstructions<'a> {
|
||||||
SeraiInInstructions(self)
|
SeraiInInstructions(self)
|
||||||
}
|
}
|
||||||
|
|
55
substrate/client/tests/common/dex.rs
Normal file
55
substrate/client/tests/common/dex.rs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
use serai_runtime::primitives::{Coin, Amount};
|
||||||
|
|
||||||
|
use serai_client::{Serai, SeraiDex, PairSigner};
|
||||||
|
use sp_core::{sr25519::Pair, Pair as PairTrait};
|
||||||
|
|
||||||
|
use subxt::config::extrinsic_params::BaseExtrinsicParamsBuilder;
|
||||||
|
|
||||||
|
use crate::common::tx::publish_tx;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn add_liquidity(
|
||||||
|
serai: &Serai,
|
||||||
|
coin: Coin,
|
||||||
|
coin_amount: Amount,
|
||||||
|
sri_amount: Amount,
|
||||||
|
nonce: u32,
|
||||||
|
pair: Pair,
|
||||||
|
) -> [u8; 32] {
|
||||||
|
let address = pair.public();
|
||||||
|
|
||||||
|
let tx = serai
|
||||||
|
.sign(
|
||||||
|
&PairSigner::new(pair),
|
||||||
|
&SeraiDex::add_liquidity(coin, coin_amount, sri_amount, Amount(1), Amount(1), address.into()),
|
||||||
|
nonce,
|
||||||
|
BaseExtrinsicParamsBuilder::new(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
publish_tx(serai, &tx).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn swap(
|
||||||
|
serai: &Serai,
|
||||||
|
from_coin: Coin,
|
||||||
|
to_coin: Coin,
|
||||||
|
amount_in: Amount,
|
||||||
|
amount_out_min: Amount,
|
||||||
|
nonce: u32,
|
||||||
|
pair: Pair,
|
||||||
|
) -> [u8; 32] {
|
||||||
|
let address = pair.public();
|
||||||
|
|
||||||
|
let tx = serai
|
||||||
|
.sign(
|
||||||
|
&PairSigner::new(pair),
|
||||||
|
&SeraiDex::swap(from_coin, to_coin, amount_in, amount_out_min, address.into()),
|
||||||
|
nonce,
|
||||||
|
BaseExtrinsicParamsBuilder::new(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
publish_tx(serai, &tx).await
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
use rand_core::{RngCore, OsRng};
|
||||||
use blake2::{
|
use blake2::{
|
||||||
digest::{consts::U32, Digest},
|
digest::{consts::U32, Digest},
|
||||||
Blake2b,
|
Blake2b,
|
||||||
|
@ -8,10 +9,10 @@ use scale::Encode;
|
||||||
use sp_core::Pair;
|
use sp_core::Pair;
|
||||||
|
|
||||||
use serai_client::{
|
use serai_client::{
|
||||||
primitives::insecure_pair_from_name,
|
primitives::{insecure_pair_from_name, BlockHash, NetworkId, Balance, SeraiAddress},
|
||||||
validator_sets::primitives::{Session, ValidatorSet},
|
validator_sets::primitives::{Session, ValidatorSet},
|
||||||
in_instructions::{
|
in_instructions::{
|
||||||
primitives::{Batch, SignedBatch, batch_message},
|
primitives::{Batch, SignedBatch, batch_message, InInstruction, InInstructionWithBalance},
|
||||||
InInstructionsEvent,
|
InInstructionsEvent,
|
||||||
},
|
},
|
||||||
SeraiInInstructions, Serai,
|
SeraiInInstructions, Serai,
|
||||||
|
@ -60,3 +61,27 @@ pub async fn provide_batch(serai: &Serai, batch: Batch) -> [u8; 32] {
|
||||||
|
|
||||||
block
|
block
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn mint_coin(
|
||||||
|
serai: &Serai,
|
||||||
|
balance: Balance,
|
||||||
|
network: NetworkId,
|
||||||
|
batch_id: u32,
|
||||||
|
address: SeraiAddress,
|
||||||
|
) -> [u8; 32] {
|
||||||
|
let mut block_hash = BlockHash([0; 32]);
|
||||||
|
OsRng.fill_bytes(&mut block_hash.0);
|
||||||
|
|
||||||
|
let batch = Batch {
|
||||||
|
network,
|
||||||
|
id: batch_id,
|
||||||
|
block: block_hash,
|
||||||
|
instructions: vec![InInstructionWithBalance {
|
||||||
|
instruction: InInstruction::Transfer(address),
|
||||||
|
balance,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
provide_batch(serai, batch).await
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
pub mod tx;
|
pub mod tx;
|
||||||
pub mod validator_sets;
|
pub mod validator_sets;
|
||||||
pub mod in_instructions;
|
pub mod in_instructions;
|
||||||
|
pub mod dex;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! serai_test {
|
macro_rules! serai_test {
|
||||||
|
|
452
substrate/client/tests/dex.rs
Normal file
452
substrate/client/tests/dex.rs
Normal file
|
@ -0,0 +1,452 @@
|
||||||
|
use rand_core::{RngCore, OsRng};
|
||||||
|
use scale::Encode;
|
||||||
|
|
||||||
|
use sp_core::{Pair as PairTrait, bounded_vec::BoundedVec, hashing::blake2_256};
|
||||||
|
|
||||||
|
use serai_runtime::in_instructions::primitives::DexCall;
|
||||||
|
|
||||||
|
use serai_client::{
|
||||||
|
primitives::{
|
||||||
|
Amount, NetworkId, Coin, Balance, BlockHash, insecure_pair_from_name, ExternalAddress,
|
||||||
|
SeraiAddress, PublicKey,
|
||||||
|
},
|
||||||
|
in_instructions::primitives::{
|
||||||
|
InInstruction, InInstructionWithBalance, Batch, IN_INSTRUCTION_EXECUTOR, OutAddress,
|
||||||
|
},
|
||||||
|
dex::DexEvent,
|
||||||
|
Serai,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
use common::{
|
||||||
|
in_instructions::{provide_batch, mint_coin},
|
||||||
|
dex::{add_liquidity as common_add_liquidity, swap as common_swap},
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Calculate all constants in the following tests
|
||||||
|
// TODO: Check LP token, coin balances
|
||||||
|
// TODO: Modularize common code
|
||||||
|
// TODO: Check Transfer events
|
||||||
|
serai_test!(
|
||||||
|
create_pool: (|serai: Serai| async move {
|
||||||
|
let block = serai.block_by_number(0).await.unwrap().unwrap().hash();
|
||||||
|
let events = serai.as_of(block).dex().all_events().await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
events,
|
||||||
|
vec![
|
||||||
|
DexEvent::PoolCreated {
|
||||||
|
pool_id: (Coin::Serai, Coin::Bitcoin),
|
||||||
|
pool_account: PublicKey::from_raw(blake2_256(&(Coin::Serai, Coin::Bitcoin).encode())),
|
||||||
|
lp_token: 0,
|
||||||
|
},
|
||||||
|
DexEvent::PoolCreated {
|
||||||
|
pool_id: (Coin::Serai, Coin::Ether),
|
||||||
|
pool_account: PublicKey::from_raw(blake2_256(&(Coin::Serai, Coin::Ether).encode())),
|
||||||
|
lp_token: 1,
|
||||||
|
},
|
||||||
|
DexEvent::PoolCreated {
|
||||||
|
pool_id: (Coin::Serai, Coin::Dai),
|
||||||
|
pool_account: PublicKey::from_raw(blake2_256(&(Coin::Serai, Coin::Dai).encode())),
|
||||||
|
lp_token: 2,
|
||||||
|
},
|
||||||
|
DexEvent::PoolCreated {
|
||||||
|
pool_id: (Coin::Serai, Coin::Monero),
|
||||||
|
pool_account: PublicKey::from_raw(blake2_256(&(Coin::Serai, Coin::Monero).encode())),
|
||||||
|
lp_token: 3,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
add_liquidity: (|serai: Serai| async move {
|
||||||
|
let coin = Coin::Monero;
|
||||||
|
let pair = insecure_pair_from_name("Ferdie");
|
||||||
|
|
||||||
|
// mint sriXMR in the account so that we can add liq.
|
||||||
|
// Ferdie account is already pre-funded with SRI.
|
||||||
|
mint_coin(
|
||||||
|
&serai,
|
||||||
|
Balance { coin, amount: Amount(100_000_000_000_000) },
|
||||||
|
NetworkId::Monero,
|
||||||
|
0,
|
||||||
|
pair.clone().public().into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// add liquidity
|
||||||
|
let coin_amount = Amount(50_000_000_000_000);
|
||||||
|
let sri_amount = Amount(50_000_000_000_000);
|
||||||
|
let block = common_add_liquidity(&serai,
|
||||||
|
coin,
|
||||||
|
coin_amount,
|
||||||
|
sri_amount,
|
||||||
|
0,
|
||||||
|
pair.clone()
|
||||||
|
).await;
|
||||||
|
// get only the add liq events
|
||||||
|
let mut events = serai.as_of(block).dex().all_events().await.unwrap();
|
||||||
|
events.retain(|e| matches!(e, DexEvent::LiquidityAdded { .. }));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
events,
|
||||||
|
vec![DexEvent::LiquidityAdded {
|
||||||
|
who: pair.public(),
|
||||||
|
mint_to: pair.public(),
|
||||||
|
pool_id: (Coin::Serai, Coin::Monero),
|
||||||
|
amount1_provided: coin_amount.0,
|
||||||
|
amount2_provided: sri_amount.0,
|
||||||
|
lp_token: 3,
|
||||||
|
lp_token_minted: 49_999999990000
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
// Tests coin -> SRI and SRI -> coin swaps.
|
||||||
|
swap_coin_to_sri: (|serai: Serai| async move {
|
||||||
|
let coin = Coin::Ether;
|
||||||
|
let pair = insecure_pair_from_name("Ferdie");
|
||||||
|
|
||||||
|
// mint sriXMR in the account so that we can add liq.
|
||||||
|
// Ferdie account is already pre-funded with SRI.
|
||||||
|
mint_coin(
|
||||||
|
&serai,
|
||||||
|
Balance { coin, amount: Amount(100_000_000_000_000) },
|
||||||
|
NetworkId::Ethereum,
|
||||||
|
0,
|
||||||
|
pair.clone().public().into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// add liquidity
|
||||||
|
common_add_liquidity(&serai,
|
||||||
|
coin,
|
||||||
|
Amount(50_000_000_000_000),
|
||||||
|
Amount(50_000_000_000_000),
|
||||||
|
0,
|
||||||
|
pair.clone()
|
||||||
|
).await;
|
||||||
|
|
||||||
|
// now that we have our liquid pool, swap some coin to SRI.
|
||||||
|
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())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// get only the swap events
|
||||||
|
let mut events = serai.as_of(block).dex().all_events().await.unwrap();
|
||||||
|
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||||
|
|
||||||
|
let mut path = BoundedVec::try_from(vec![coin, Coin::Serai]).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
events,
|
||||||
|
vec![DexEvent::SwapExecuted {
|
||||||
|
who: pair.clone().public(),
|
||||||
|
send_to: pair.public(),
|
||||||
|
path,
|
||||||
|
amount_in: amount_in.0,
|
||||||
|
amount_out: 16633299966633
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
|
||||||
|
// now swap some SRI to coin
|
||||||
|
amount_in = Amount(10_000_000_000_000);
|
||||||
|
block = common_swap(&serai, Coin::Serai, coin, amount_in, Amount(1), 2, pair.clone()).await;
|
||||||
|
|
||||||
|
// get only the swap events
|
||||||
|
let mut events = serai.as_of(block).dex().all_events().await.unwrap();
|
||||||
|
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||||
|
|
||||||
|
path = BoundedVec::try_from(vec![Coin::Serai, coin]).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
events,
|
||||||
|
vec![DexEvent::SwapExecuted {
|
||||||
|
who: pair.clone().public(),
|
||||||
|
send_to: pair.public(),
|
||||||
|
path,
|
||||||
|
amount_in: amount_in.0,
|
||||||
|
amount_out: 17254428681101
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
swap_coin_to_coin: (|serai: Serai| async move {
|
||||||
|
let coin1 = Coin::Monero;
|
||||||
|
let coin2 = Coin::Dai;
|
||||||
|
let pair = insecure_pair_from_name("Ferdie");
|
||||||
|
|
||||||
|
// mint coins
|
||||||
|
mint_coin(
|
||||||
|
&serai,
|
||||||
|
Balance { coin: coin1, amount: Amount(100_000_000_000_000) },
|
||||||
|
NetworkId::Monero,
|
||||||
|
0,
|
||||||
|
pair.clone().public().into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
mint_coin(
|
||||||
|
&serai,
|
||||||
|
Balance { coin: coin2, amount: Amount(100_000_000_000_000) },
|
||||||
|
NetworkId::Ethereum,
|
||||||
|
0,
|
||||||
|
pair.clone().public().into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// add liquidity to pools
|
||||||
|
common_add_liquidity(&serai,
|
||||||
|
coin1,
|
||||||
|
Amount(50_000_000_000_000),
|
||||||
|
Amount(50_000_000_000_000),
|
||||||
|
0,
|
||||||
|
pair.clone()
|
||||||
|
).await;
|
||||||
|
common_add_liquidity(&serai,
|
||||||
|
coin2,
|
||||||
|
Amount(50_000_000_000_000),
|
||||||
|
Amount(50_000_000_000_000),
|
||||||
|
1,
|
||||||
|
pair.clone()
|
||||||
|
).await;
|
||||||
|
|
||||||
|
// swap coin1 -> coin2
|
||||||
|
let amount_in = Amount(25_000_000_000_000);
|
||||||
|
let block = common_swap(&serai, coin1, coin2, amount_in, Amount(1), 2, pair.clone()).await;
|
||||||
|
|
||||||
|
// get only the swap events
|
||||||
|
let mut events = serai.as_of(block).dex().all_events().await.unwrap();
|
||||||
|
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||||
|
|
||||||
|
let path = BoundedVec::try_from(vec![coin1, Coin::Serai, coin2]).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
events,
|
||||||
|
vec![DexEvent::SwapExecuted {
|
||||||
|
who: pair.clone().public(),
|
||||||
|
send_to: pair.public(),
|
||||||
|
path,
|
||||||
|
amount_in: amount_in.0,
|
||||||
|
amount_out: 12453103964435,
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
add_liquidity_in_instructions: (|serai: Serai| async move {
|
||||||
|
let coin = Coin::Bitcoin;
|
||||||
|
let pair = insecure_pair_from_name("Ferdie");
|
||||||
|
let mut batch_id = 0;
|
||||||
|
|
||||||
|
// mint sriBTC in the account so that we can add liq.
|
||||||
|
// Ferdie account is already pre-funded with SRI.
|
||||||
|
mint_coin(
|
||||||
|
&serai,
|
||||||
|
Balance { coin, amount: Amount(100_000_000_000_000) },
|
||||||
|
NetworkId::Bitcoin,
|
||||||
|
batch_id,
|
||||||
|
pair.clone().public().into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
batch_id += 1;
|
||||||
|
|
||||||
|
// add liquidity
|
||||||
|
common_add_liquidity(&serai,
|
||||||
|
coin,
|
||||||
|
Amount(50_000_000_000_000),
|
||||||
|
Amount(50_000_000_000_000),
|
||||||
|
0,
|
||||||
|
pair.clone()
|
||||||
|
).await;
|
||||||
|
|
||||||
|
// now that we have our liquid SRI/BTC pool, we can add more liquidity to it via an
|
||||||
|
// InInstruction
|
||||||
|
let mut block_hash = BlockHash([0; 32]);
|
||||||
|
OsRng.fill_bytes(&mut block_hash.0);
|
||||||
|
let batch = Batch {
|
||||||
|
network: NetworkId::Bitcoin,
|
||||||
|
id: batch_id,
|
||||||
|
block: block_hash,
|
||||||
|
instructions: vec![InInstructionWithBalance {
|
||||||
|
instruction: InInstruction::Dex(DexCall::SwapAndAddLiquidity(pair.public().into())),
|
||||||
|
balance: Balance { coin: Coin::Bitcoin, amount: Amount(20_000_000_000_000) },
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let block = provide_batch(&serai, batch).await;
|
||||||
|
let mut events = serai.as_of(block).dex().all_events().await.unwrap();
|
||||||
|
events.retain(|e| matches!(e, DexEvent::LiquidityAdded { .. }));
|
||||||
|
assert_eq!(
|
||||||
|
events,
|
||||||
|
vec![DexEvent::LiquidityAdded {
|
||||||
|
who: IN_INSTRUCTION_EXECUTOR.into(),
|
||||||
|
mint_to: pair.public(),
|
||||||
|
pool_id: (Coin::Serai, Coin::Bitcoin),
|
||||||
|
amount1_provided: 6_947_918_403_646,
|
||||||
|
amount2_provided: 10_000_000_000_000, // half of sent amount
|
||||||
|
lp_token: 0,
|
||||||
|
lp_token_minted: 8333333333332
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
swap_in_instructions: (|serai: Serai| async move {
|
||||||
|
let coin1 = Coin::Monero;
|
||||||
|
let coin2 = Coin::Ether;
|
||||||
|
let pair = insecure_pair_from_name("Ferdie");
|
||||||
|
let mut coin1_batch_id = 0;
|
||||||
|
let mut coin2_batch_id = 0;
|
||||||
|
|
||||||
|
// mint coins
|
||||||
|
mint_coin(
|
||||||
|
&serai,
|
||||||
|
Balance { coin: coin1, amount: Amount(100_000_000_000_000) },
|
||||||
|
NetworkId::Monero,
|
||||||
|
coin1_batch_id,
|
||||||
|
pair.clone().public().into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
coin1_batch_id += 1;
|
||||||
|
mint_coin(
|
||||||
|
&serai,
|
||||||
|
Balance { coin: coin2, amount: Amount(100_000_000_000_000) },
|
||||||
|
NetworkId::Ethereum,
|
||||||
|
coin2_batch_id,
|
||||||
|
pair.clone().public().into(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
coin2_batch_id += 1;
|
||||||
|
|
||||||
|
// add liquidity to pools
|
||||||
|
common_add_liquidity(&serai,
|
||||||
|
coin1,
|
||||||
|
Amount(50_000_000_000_000),
|
||||||
|
Amount(50_000_000_000_000),
|
||||||
|
0,
|
||||||
|
pair.clone()
|
||||||
|
).await;
|
||||||
|
common_add_liquidity(&serai,
|
||||||
|
coin2,
|
||||||
|
Amount(50_000_000_000_000),
|
||||||
|
Amount(50_000_000_000_000),
|
||||||
|
1,
|
||||||
|
pair.clone()
|
||||||
|
).await;
|
||||||
|
|
||||||
|
// rand address bytes
|
||||||
|
let mut rand_bytes = vec![0; 32];
|
||||||
|
OsRng.fill_bytes(&mut rand_bytes);
|
||||||
|
|
||||||
|
// XMR -> ETH
|
||||||
|
{
|
||||||
|
// make an out address
|
||||||
|
let out_address = OutAddress::External(ExternalAddress::new(rand_bytes.clone()).unwrap());
|
||||||
|
|
||||||
|
// amount is the min out amount
|
||||||
|
let out_balance = Balance { coin: coin2, amount: Amount(1) };
|
||||||
|
|
||||||
|
// now that we have our pools, we can try to swap
|
||||||
|
let mut block_hash = BlockHash([0; 32]);
|
||||||
|
OsRng.fill_bytes(&mut block_hash.0);
|
||||||
|
let batch = Batch {
|
||||||
|
network: NetworkId::Monero,
|
||||||
|
id: coin1_batch_id,
|
||||||
|
block: block_hash,
|
||||||
|
instructions: vec![InInstructionWithBalance {
|
||||||
|
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address)),
|
||||||
|
balance: Balance { coin: coin1, amount: Amount(20_000_000_000_000) },
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let block = provide_batch(&serai, batch).await;
|
||||||
|
coin1_batch_id += 1;
|
||||||
|
let mut events = serai.as_of(block).dex().all_events().await.unwrap();
|
||||||
|
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||||
|
|
||||||
|
let path = BoundedVec::try_from(vec![coin1, Coin::Serai, coin2]).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
events,
|
||||||
|
vec![DexEvent::SwapExecuted {
|
||||||
|
who: IN_INSTRUCTION_EXECUTOR.into(),
|
||||||
|
send_to: IN_INSTRUCTION_EXECUTOR.into(),
|
||||||
|
path,
|
||||||
|
amount_in: 20_000_000_000_000,
|
||||||
|
amount_out: 11066655622377
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ETH -> sriXMR
|
||||||
|
{
|
||||||
|
// make an out address
|
||||||
|
let out_address =
|
||||||
|
OutAddress::Serai(SeraiAddress::new(rand_bytes.clone().try_into().unwrap()));
|
||||||
|
|
||||||
|
// amount is the min out amount
|
||||||
|
let out_balance = Balance { coin: coin1, amount: Amount(1) };
|
||||||
|
|
||||||
|
// now that we have our pools, we can try to swap
|
||||||
|
let mut block_hash = BlockHash([0; 32]);
|
||||||
|
OsRng.fill_bytes(&mut block_hash.0);
|
||||||
|
let batch = Batch {
|
||||||
|
network: NetworkId::Ethereum,
|
||||||
|
id: coin2_batch_id,
|
||||||
|
block: block_hash,
|
||||||
|
instructions: vec![InInstructionWithBalance {
|
||||||
|
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address.clone())),
|
||||||
|
balance: Balance { coin: coin2, amount: Amount(20_000_000_000_000) },
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let block = provide_batch(&serai, batch).await;
|
||||||
|
let mut events = serai.as_of(block).dex().all_events().await.unwrap();
|
||||||
|
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||||
|
|
||||||
|
let path = BoundedVec::try_from(vec![coin2, Coin::Serai, coin1]).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
events,
|
||||||
|
vec![DexEvent::SwapExecuted {
|
||||||
|
who: IN_INSTRUCTION_EXECUTOR.into(),
|
||||||
|
send_to: out_address.as_native().unwrap().into(),
|
||||||
|
path,
|
||||||
|
amount_in: 20_000_000_000_000,
|
||||||
|
amount_out: 26440798801319
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// XMR -> SRI
|
||||||
|
{
|
||||||
|
// make an out address
|
||||||
|
let out_address = OutAddress::Serai(SeraiAddress::new(rand_bytes.try_into().unwrap()));
|
||||||
|
|
||||||
|
// amount is the min out amount
|
||||||
|
let out_balance = Balance { coin: Coin::Serai, amount: Amount(1) };
|
||||||
|
|
||||||
|
// now that we have our pools, we can try to swap
|
||||||
|
let mut block_hash = BlockHash([0; 32]);
|
||||||
|
OsRng.fill_bytes(&mut block_hash.0);
|
||||||
|
let batch = Batch {
|
||||||
|
network: NetworkId::Monero,
|
||||||
|
id: coin1_batch_id,
|
||||||
|
block: block_hash,
|
||||||
|
instructions: vec![InInstructionWithBalance {
|
||||||
|
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address.clone())),
|
||||||
|
balance: Balance { coin: coin1, amount: Amount(10_000_000_000_000) },
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let block = provide_batch(&serai, batch).await;
|
||||||
|
let mut events = serai.as_of(block).dex().all_events().await.unwrap();
|
||||||
|
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));
|
||||||
|
|
||||||
|
let path = BoundedVec::try_from(vec![coin1, Coin::Serai]).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
events,
|
||||||
|
vec![DexEvent::SwapExecuted {
|
||||||
|
who: IN_INSTRUCTION_EXECUTOR.into(),
|
||||||
|
send_to: out_address.as_native().unwrap().into(),
|
||||||
|
path,
|
||||||
|
amount_in: 10_000_000_000_000,
|
||||||
|
amount_out: 10711005507065
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
|
@ -24,6 +24,8 @@ sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features
|
||||||
|
|
||||||
pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
|
dex-primitives = { package = "serai-dex-primitives", path = "../../dex/primitives", default-features = false }
|
||||||
|
|
||||||
serai-primitives = { path = "../../primitives", default-features = false }
|
serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
coins-primitives = { package = "serai-coins-primitives", path = "../primitives", default-features = false }
|
coins-primitives = { package = "serai-coins-primitives", path = "../primitives", default-features = false }
|
||||||
|
|
||||||
|
@ -38,6 +40,8 @@ std = [
|
||||||
|
|
||||||
"pallet-transaction-payment/std",
|
"pallet-transaction-payment/std",
|
||||||
|
|
||||||
|
"dex-primitives/std",
|
||||||
|
|
||||||
"serai-primitives/std",
|
"serai-primitives/std",
|
||||||
"coins-primitives/std",
|
"coins-primitives/std",
|
||||||
]
|
]
|
||||||
|
|
|
@ -14,12 +14,14 @@ pub mod pallet {
|
||||||
|
|
||||||
use pallet_transaction_payment::{Config as TpConfig, OnChargeTransaction};
|
use pallet_transaction_payment::{Config as TpConfig, OnChargeTransaction};
|
||||||
|
|
||||||
|
use dex_primitives::{Currency, Coins as CoinsTrait};
|
||||||
|
|
||||||
use serai_primitives::*;
|
use serai_primitives::*;
|
||||||
pub use coins_primitives as primitives;
|
pub use coins_primitives as primitives;
|
||||||
use primitives::*;
|
use primitives::*;
|
||||||
|
|
||||||
#[pallet::config]
|
#[pallet::config]
|
||||||
pub trait Config: frame_system::Config<AccountId = Public> + TpConfig {
|
pub trait Config: frame_system::Config<AccountId = Public> {
|
||||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,12 +74,13 @@ pub mod pallet {
|
||||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||||
fn build(&self) {
|
fn build(&self) {
|
||||||
// initialize the supply of the coins
|
// initialize the supply of the coins
|
||||||
for c in COINS.iter() {
|
// TODO: Don't use COINS yet GenesisConfig so we can safely expand COINS
|
||||||
|
for c in &COINS {
|
||||||
Supply::<T>::set(c, 0);
|
Supply::<T>::set(c, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize the genesis accounts
|
// initialize the genesis accounts
|
||||||
for (account, balance) in self.accounts.iter() {
|
for (account, balance) in &self.accounts {
|
||||||
Pallet::<T>::mint(*account, *balance).unwrap();
|
Pallet::<T>::mint(*account, *balance).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,7 +217,75 @@ pub mod pallet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> OnChargeTransaction<T> for Pallet<T> {
|
impl<T: Config> Currency<T::AccountId> for Pallet<T> {
|
||||||
|
type Balance = SubstrateAmount;
|
||||||
|
|
||||||
|
fn balance(of: &Public) -> Self::Balance {
|
||||||
|
Self::balance(*of, Coin::Serai).0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TODO: make sure of coin precision here.
|
||||||
|
fn minimum_balance() -> Self::Balance {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer(
|
||||||
|
from: &Public,
|
||||||
|
to: &Public,
|
||||||
|
amount: Self::Balance,
|
||||||
|
) -> Result<Self::Balance, DispatchError> {
|
||||||
|
let balance = Balance { coin: Coin::Serai, amount: Amount(amount) };
|
||||||
|
Self::transfer_internal(*from, *to, balance)?;
|
||||||
|
Ok(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mint(to: &Public, amount: Self::Balance) -> Result<Self::Balance, DispatchError> {
|
||||||
|
Self::mint(*to, Balance { coin: Coin::native(), amount: Amount(amount) })?;
|
||||||
|
Ok(amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Have DEX implement for Coins, not Coins implement for Coins
|
||||||
|
impl<T: Config> CoinsTrait<T::AccountId> for Pallet<T> {
|
||||||
|
type Balance = SubstrateAmount;
|
||||||
|
type CoinId = Coin;
|
||||||
|
|
||||||
|
// TODO: Swap the order of these arguments
|
||||||
|
fn balance(coin: Self::CoinId, of: &Public) -> Self::Balance {
|
||||||
|
Self::balance(*of, coin).0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn minimum_balance(_: Self::CoinId) -> Self::Balance {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Move coin next to amount
|
||||||
|
fn transfer(
|
||||||
|
coin: Self::CoinId,
|
||||||
|
from: &Public,
|
||||||
|
to: &Public,
|
||||||
|
amount: Self::Balance,
|
||||||
|
) -> Result<Self::Balance, DispatchError> {
|
||||||
|
let balance = Balance { coin, amount: Amount(amount) };
|
||||||
|
Self::transfer_internal(*from, *to, balance)?;
|
||||||
|
Ok(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Move coin next to amount
|
||||||
|
fn mint(
|
||||||
|
coin: Self::CoinId,
|
||||||
|
to: &Public,
|
||||||
|
amount: Self::Balance,
|
||||||
|
) -> Result<Self::Balance, DispatchError> {
|
||||||
|
Self::mint(*to, Balance { coin, amount: Amount(amount) })?;
|
||||||
|
Ok(amount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Config> OnChargeTransaction<T> for Pallet<T>
|
||||||
|
where
|
||||||
|
T: TpConfig,
|
||||||
|
{
|
||||||
type Balance = SubstrateAmount;
|
type Balance = SubstrateAmount;
|
||||||
type LiquidityInfo = Option<SubstrateAmount>;
|
type LiquidityInfo = Option<SubstrateAmount>;
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ use scale_info::TypeInfo;
|
||||||
|
|
||||||
use serai_primitives::{Balance, SeraiAddress, ExternalAddress, Data, system_address};
|
use serai_primitives::{Balance, SeraiAddress, ExternalAddress, Data, system_address};
|
||||||
|
|
||||||
pub const FEE_ACCOUNT: SeraiAddress = system_address(b"FeeAccount");
|
pub const FEE_ACCOUNT: SeraiAddress = system_address(b"Coins-fees");
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Encode, Decode, MaxEncodedLen, TypeInfo,
|
Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Encode, Decode, MaxEncodedLen, TypeInfo,
|
||||||
|
@ -46,6 +46,6 @@ fn address() {
|
||||||
use sp_runtime::traits::TrailingZeroInput;
|
use sp_runtime::traits::TrailingZeroInput;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
FEE_ACCOUNT,
|
FEE_ACCOUNT,
|
||||||
SeraiAddress::decode(&mut TrailingZeroInput::new(b"FeeAccount")).unwrap()
|
SeraiAddress::decode(&mut TrailingZeroInput::new(b"Coins-fees")).unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
75
substrate/dex/pallet/Cargo.toml
Normal file
75
substrate/dex/pallet/Cargo.toml
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
[package]
|
||||||
|
name = "serai-dex-pallet"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "DEX pallet for Serai"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/dex/pallet"
|
||||||
|
authors = ["Parity Technologies <admin@parity.io>, Akil Demir <aeg_asd@hotmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
|
||||||
|
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
|
||||||
|
|
||||||
|
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
sp-arithmetic = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
sp-io = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
sp-api = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
|
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
frame-benchmarking = { git = "https://github.com/serai-dex/substrate", default-features = false, optional = true }
|
||||||
|
|
||||||
|
dex-primitives = { package = "serai-dex-primitives", path = "../primitives", default-features = false }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
|
|
||||||
|
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
|
||||||
|
liquidity-tokens-pallet = { package = "serai-liquidity-tokens-pallet", path = "../../liquidity-tokens/pallet", default-features = false }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["std"]
|
||||||
|
std = [
|
||||||
|
"codec/std",
|
||||||
|
"scale-info/std",
|
||||||
|
|
||||||
|
"sp-std/std",
|
||||||
|
"sp-arithmetic/std",
|
||||||
|
"sp-io/std",
|
||||||
|
"sp-api/std",
|
||||||
|
"sp-runtime/std",
|
||||||
|
"sp-core/std",
|
||||||
|
|
||||||
|
"serai-primitives/std",
|
||||||
|
|
||||||
|
"dex-primitives/std",
|
||||||
|
|
||||||
|
"frame-system/std",
|
||||||
|
"frame-support/std",
|
||||||
|
"frame-benchmarking?/std",
|
||||||
|
|
||||||
|
"coins-pallet/std",
|
||||||
|
"liquidity-tokens-pallet/std",
|
||||||
|
]
|
||||||
|
runtime-benchmarks = [
|
||||||
|
"sp-runtime/runtime-benchmarks",
|
||||||
|
|
||||||
|
"frame-system/runtime-benchmarks",
|
||||||
|
"frame-support/runtime-benchmarks",
|
||||||
|
"frame-benchmarking/runtime-benchmarks",
|
||||||
|
|
||||||
|
"dex-primitives/runtime-benchmarks",
|
||||||
|
]
|
||||||
|
try-runtime = [
|
||||||
|
"sp-runtime/try-runtime",
|
||||||
|
|
||||||
|
"frame-system/try-runtime",
|
||||||
|
"frame-support/try-runtime",
|
||||||
|
]
|
15
substrate/dex/pallet/LICENSE-AGPL3
Normal file
15
substrate/dex/pallet/LICENSE-AGPL3
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
AGPL-3.0-only license
|
||||||
|
|
||||||
|
Copyright (c) 2023 Luke Parker
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License Version 3 as
|
||||||
|
published by the Free Software Foundation.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
211
substrate/dex/pallet/LICENSE-APACHE2
Normal file
211
substrate/dex/pallet/LICENSE-APACHE2
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
NOTE
|
||||||
|
|
||||||
|
Individual files contain the following tag instead of the full license
|
||||||
|
text.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
This enables machine processing of license information based on the SPDX
|
||||||
|
License Identifiers that are here available: http://spdx.org/licenses/
|
271
substrate/dex/pallet/src/benchmarking.rs
Normal file
271
substrate/dex/pallet/src/benchmarking.rs
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
// This file was originally:
|
||||||
|
|
||||||
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//! Dex pallet benchmarking.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use frame_benchmarking::{benchmarks, whitelisted_caller};
|
||||||
|
use frame_support::{assert_ok, storage::bounded_vec::BoundedVec};
|
||||||
|
use frame_system::RawOrigin as SystemOrigin;
|
||||||
|
use sp_runtime::traits::{Bounded, StaticLookup};
|
||||||
|
use sp_std::{ops::Div, prelude::*};
|
||||||
|
|
||||||
|
use crate::Pallet as Dex;
|
||||||
|
|
||||||
|
const INITIAL_COIN_BALANCE: u64 = 1_000_000_000;
|
||||||
|
type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
|
||||||
|
type BalanceOf<T> =
|
||||||
|
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||||
|
|
||||||
|
fn get_lp_token_id<T: Config>() -> T::PoolCoinId
|
||||||
|
where
|
||||||
|
T::PoolCoinId: Into<u32>,
|
||||||
|
{
|
||||||
|
let next_id: u32 = Dex::<T>::get_next_pool_coin_id().into();
|
||||||
|
(next_id - 1).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_coin<T: Config>(coin: &T::MultiCoinId) -> (T::AccountId, AccountIdLookupOf<T>)
|
||||||
|
where
|
||||||
|
T::CoinBalance: From<u64>,
|
||||||
|
T::Currency: Currency<T::AccountId>,
|
||||||
|
T::Coins: Coins<T::AccountId>,
|
||||||
|
{
|
||||||
|
let caller: T::AccountId = whitelisted_caller();
|
||||||
|
let caller_lookup = T::Lookup::unlookup(caller.clone());
|
||||||
|
if let MultiCoinIdConversionResult::Converted(coin_id) =
|
||||||
|
T::MultiCoinIdConverter::try_convert(coin)
|
||||||
|
{
|
||||||
|
assert_ok!(T::Currency::mint(&caller, BalanceOf::<T>::max_value().div(1000u32.into())));
|
||||||
|
assert_ok!(T::Coins::mint(coin_id, &caller, INITIAL_COIN_BALANCE.into()));
|
||||||
|
}
|
||||||
|
(caller, caller_lookup)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_coin_and_pool<T: Config>(
|
||||||
|
coin1: &T::MultiCoinId,
|
||||||
|
coin2: &T::MultiCoinId,
|
||||||
|
) -> (T::PoolCoinId, T::AccountId, AccountIdLookupOf<T>)
|
||||||
|
where
|
||||||
|
T::CoinBalance: From<u64>,
|
||||||
|
T::Currency: Currency<T::AccountId>,
|
||||||
|
T::Coins: Coins<T::AccountId>,
|
||||||
|
T::PoolCoinId: Into<u32>,
|
||||||
|
{
|
||||||
|
assert_eq!(coin1, &T::MultiCoinIdConverter::get_native());
|
||||||
|
|
||||||
|
let (caller, caller_lookup) = create_coin::<T>(coin2);
|
||||||
|
|
||||||
|
assert_ok!(Dex::<T>::create_pool(coin2.clone()));
|
||||||
|
let lp_token = get_lp_token_id::<T>();
|
||||||
|
|
||||||
|
(lp_token, caller, caller_lookup)
|
||||||
|
}
|
||||||
|
|
||||||
|
benchmarks! {
|
||||||
|
where_clause {
|
||||||
|
where
|
||||||
|
T::CoinBalance: From<u64> + Into<u64>,
|
||||||
|
T::Currency: Currency<T::AccountId>,
|
||||||
|
T::Balance: From<u64> + Into<u64>,
|
||||||
|
T::Coins: Coins<T::AccountId>,
|
||||||
|
T::PoolCoinId: Into<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
add_liquidity {
|
||||||
|
let coin1 = T::MultiCoinIdConverter::get_native();
|
||||||
|
let coin2: T::MultiCoinId = T::BenchmarkHelper::coin_id(0).into();
|
||||||
|
let (lp_token, caller, _) = create_coin_and_pool::<T>(&coin1, &coin2);
|
||||||
|
let ed: u64 = T::Currency::minimum_balance().into();
|
||||||
|
let add_amount = 1000 + ed;
|
||||||
|
}: _(
|
||||||
|
SystemOrigin::Signed(caller.clone()),
|
||||||
|
coin1.clone(),
|
||||||
|
coin2.clone(),
|
||||||
|
add_amount.into(),
|
||||||
|
1000.into(),
|
||||||
|
0.into(),
|
||||||
|
0.into(),
|
||||||
|
caller.clone()
|
||||||
|
)
|
||||||
|
verify {
|
||||||
|
let pool_id = (coin1.clone(), coin2.clone());
|
||||||
|
let lp_minted = Dex::<T>::calc_lp_amount_for_zero_supply(
|
||||||
|
&add_amount.into(),
|
||||||
|
&1000.into()
|
||||||
|
).unwrap().into();
|
||||||
|
assert_eq!(
|
||||||
|
T::PoolCoins::balance(lp_token, &caller),
|
||||||
|
lp_minted.into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
T::Currency::balance(&Dex::<T>::get_pool_account(&pool_id)),
|
||||||
|
add_amount.into()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
T::Coins::balance(
|
||||||
|
T::BenchmarkHelper::coin_id(0),
|
||||||
|
&Dex::<T>::get_pool_account(&pool_id)
|
||||||
|
),
|
||||||
|
1000.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_liquidity {
|
||||||
|
let coin1 = T::MultiCoinIdConverter::get_native();
|
||||||
|
let coin2: T::MultiCoinId = T::BenchmarkHelper::coin_id(0).into();
|
||||||
|
let (lp_token, caller, _) = create_coin_and_pool::<T>(&coin1, &coin2);
|
||||||
|
let ed: u64 = T::Currency::minimum_balance().into();
|
||||||
|
let add_amount = 100 * ed;
|
||||||
|
let lp_minted = Dex::<T>::calc_lp_amount_for_zero_supply(
|
||||||
|
&add_amount.into(),
|
||||||
|
&1000.into()
|
||||||
|
).unwrap().into();
|
||||||
|
let remove_lp_amount = lp_minted.checked_div(10).unwrap();
|
||||||
|
|
||||||
|
Dex::<T>::add_liquidity(
|
||||||
|
SystemOrigin::Signed(caller.clone()).into(),
|
||||||
|
coin1.clone(),
|
||||||
|
coin2.clone(),
|
||||||
|
add_amount.into(),
|
||||||
|
1000.into(),
|
||||||
|
0.into(),
|
||||||
|
0.into(),
|
||||||
|
caller.clone(),
|
||||||
|
)?;
|
||||||
|
let total_supply =
|
||||||
|
<T::PoolCoins as LiquidityTokens<T::AccountId>>::total_issuance(lp_token.clone());
|
||||||
|
}: _(
|
||||||
|
SystemOrigin::Signed(caller.clone()),
|
||||||
|
coin1,
|
||||||
|
coin2,
|
||||||
|
remove_lp_amount.into(),
|
||||||
|
0.into(),
|
||||||
|
0.into(),
|
||||||
|
caller.clone()
|
||||||
|
)
|
||||||
|
verify {
|
||||||
|
let new_total_supply =
|
||||||
|
<T::PoolCoins as LiquidityTokens<T::AccountId>>::total_issuance(lp_token.clone());
|
||||||
|
assert_eq!(
|
||||||
|
new_total_supply,
|
||||||
|
total_supply - remove_lp_amount.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
swap_exact_tokens_for_tokens {
|
||||||
|
let native = T::MultiCoinIdConverter::get_native();
|
||||||
|
let coin1: T::MultiCoinId = T::BenchmarkHelper::coin_id(1).into();
|
||||||
|
let coin2: T::MultiCoinId = T::BenchmarkHelper::coin_id(2).into();
|
||||||
|
let (_, caller, _) = create_coin_and_pool::<T>(&native, &coin1);
|
||||||
|
let (_, _) = create_coin::<T>(&coin2);
|
||||||
|
let ed: u64 = T::Currency::minimum_balance().into();
|
||||||
|
let ed_bump = 2u64;
|
||||||
|
|
||||||
|
Dex::<T>::add_liquidity(
|
||||||
|
SystemOrigin::Signed(caller.clone()).into(),
|
||||||
|
native.clone(),
|
||||||
|
coin1.clone(),
|
||||||
|
// TODO: this call otherwise fails with `InsufficientLiquidityMinted`.
|
||||||
|
// might be again related to their expectance on ed being > 1.
|
||||||
|
(100 * (ed + ed_bump)).into(),
|
||||||
|
200.into(),
|
||||||
|
0.into(),
|
||||||
|
0.into(),
|
||||||
|
caller.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let swap_amount = 100.into();
|
||||||
|
|
||||||
|
// since we only allow the native-coin pools, then the worst case scenario would be to swap
|
||||||
|
// coin1-native-coin2
|
||||||
|
Dex::<T>::create_pool(coin2.clone())?;
|
||||||
|
Dex::<T>::add_liquidity(
|
||||||
|
SystemOrigin::Signed(caller.clone()).into(),
|
||||||
|
native.clone(),
|
||||||
|
coin2.clone(),
|
||||||
|
(500 * ed).into(),
|
||||||
|
1000.into(),
|
||||||
|
0.into(),
|
||||||
|
0.into(),
|
||||||
|
caller.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let path = vec![coin1.clone(), native.clone(), coin2.clone()];
|
||||||
|
let path = BoundedVec::<_, T::MaxSwapPathLength>::try_from(path).unwrap();
|
||||||
|
let native_balance = T::Currency::balance(&caller);
|
||||||
|
let coin1_balance = T::Coins::balance(T::BenchmarkHelper::coin_id(1), &caller);
|
||||||
|
}: _(SystemOrigin::Signed(caller.clone()), path, swap_amount, 1.into(), caller.clone())
|
||||||
|
verify {
|
||||||
|
let ed_bump = 2u64;
|
||||||
|
let new_coin1_balance = T::Coins::balance(T::BenchmarkHelper::coin_id(1), &caller);
|
||||||
|
assert_eq!(new_coin1_balance, coin1_balance - 100.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
swap_tokens_for_exact_tokens {
|
||||||
|
let native = T::MultiCoinIdConverter::get_native();
|
||||||
|
let coin1: T::MultiCoinId = T::BenchmarkHelper::coin_id(1).into();
|
||||||
|
let coin2: T::MultiCoinId = T::BenchmarkHelper::coin_id(2).into();
|
||||||
|
let (_, caller, _) = create_coin_and_pool::<T>(&native, &coin1);
|
||||||
|
let (_, _) = create_coin::<T>(&coin2);
|
||||||
|
let ed: u64 = T::Currency::minimum_balance().into();
|
||||||
|
|
||||||
|
Dex::<T>::add_liquidity(
|
||||||
|
SystemOrigin::Signed(caller.clone()).into(),
|
||||||
|
native.clone(),
|
||||||
|
coin1.clone(),
|
||||||
|
(1000 * ed).into(),
|
||||||
|
500.into(),
|
||||||
|
0.into(),
|
||||||
|
0.into(),
|
||||||
|
caller.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// since we only allow the native-coin pools, then the worst case scenario would be to swap
|
||||||
|
// coin1-native-coin2
|
||||||
|
Dex::<T>::create_pool(coin2.clone())?;
|
||||||
|
Dex::<T>::add_liquidity(
|
||||||
|
SystemOrigin::Signed(caller.clone()).into(),
|
||||||
|
native.clone(),
|
||||||
|
coin2.clone(),
|
||||||
|
(500 * ed).into(),
|
||||||
|
1000.into(),
|
||||||
|
0.into(),
|
||||||
|
0.into(),
|
||||||
|
caller.clone(),
|
||||||
|
)?;
|
||||||
|
let path = vec![coin1.clone(), native.clone(), coin2.clone()];
|
||||||
|
|
||||||
|
let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap();
|
||||||
|
let coin2_balance = T::Coins::balance(T::BenchmarkHelper::coin_id(2), &caller);
|
||||||
|
}: _(
|
||||||
|
SystemOrigin::Signed(caller.clone()),
|
||||||
|
path.clone(),
|
||||||
|
100.into(),
|
||||||
|
(1000 * ed).into(),
|
||||||
|
caller.clone()
|
||||||
|
)
|
||||||
|
verify {
|
||||||
|
let new_coin2_balance = T::Coins::balance(T::BenchmarkHelper::coin_id(2), &caller);
|
||||||
|
assert_eq!(new_coin2_balance, coin2_balance + 100.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_benchmark_test_suite!(Dex, crate::mock::new_test_ext(), crate::mock::Test);
|
||||||
|
}
|
1217
substrate/dex/pallet/src/lib.rs
Normal file
1217
substrate/dex/pallet/src/lib.rs
Normal file
File diff suppressed because it is too large
Load diff
155
substrate/dex/pallet/src/mock.rs
Normal file
155
substrate/dex/pallet/src/mock.rs
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
// This file was originally:
|
||||||
|
|
||||||
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//! Test environment for Dex pallet.
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate as dex;
|
||||||
|
|
||||||
|
use frame_support::{
|
||||||
|
construct_runtime,
|
||||||
|
traits::{ConstU32, ConstU64},
|
||||||
|
};
|
||||||
|
|
||||||
|
use sp_core::{H256, sr25519::Public};
|
||||||
|
use sp_runtime::{
|
||||||
|
traits::{BlakeTwo256, IdentityLookup},
|
||||||
|
BuildStorage,
|
||||||
|
};
|
||||||
|
|
||||||
|
use serai_primitives::{Coin, Balance, Amount, system_address};
|
||||||
|
|
||||||
|
pub use coins_pallet as coins;
|
||||||
|
pub use liquidity_tokens_pallet as liquidity_tokens;
|
||||||
|
|
||||||
|
type Block = frame_system::mocking::MockBlock<Test>;
|
||||||
|
|
||||||
|
construct_runtime!(
|
||||||
|
pub enum Test
|
||||||
|
{
|
||||||
|
System: frame_system,
|
||||||
|
CoinsPallet: coins,
|
||||||
|
LiquidityTokens: liquidity_tokens,
|
||||||
|
Dex: dex,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
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 coins::Config for Test {
|
||||||
|
type RuntimeEvent = RuntimeEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl liquidity_tokens::Config for Test {
|
||||||
|
type RuntimeEvent = RuntimeEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CoinConverter;
|
||||||
|
impl MultiCoinIdConverter<Coin, Coin> for CoinConverter {
|
||||||
|
/// Returns the MultiCoinId representing the native currency of the chain.
|
||||||
|
fn get_native() -> Coin {
|
||||||
|
Coin::Serai
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the given MultiCoinId is the native currency.
|
||||||
|
fn is_native(coin: &Coin) -> bool {
|
||||||
|
coin.is_native()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If it's not native, returns the CoinId for the given MultiCoinId.
|
||||||
|
fn try_convert(coin: &Coin) -> MultiCoinIdConversionResult<Coin, Coin> {
|
||||||
|
if coin.is_native() {
|
||||||
|
MultiCoinIdConversionResult::Native
|
||||||
|
} else {
|
||||||
|
MultiCoinIdConversionResult::Converted(*coin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config for Test {
|
||||||
|
type RuntimeEvent = RuntimeEvent;
|
||||||
|
type Currency = CoinsPallet;
|
||||||
|
type CoinBalance = u64;
|
||||||
|
type CoinId = Coin;
|
||||||
|
type PoolCoinId = u32;
|
||||||
|
type Coins = CoinsPallet;
|
||||||
|
type PoolCoins = LiquidityTokens;
|
||||||
|
type WeightInfo = ();
|
||||||
|
type LPFee = ConstU32<3>; // means 0.3%
|
||||||
|
type MaxSwapPathLength = ConstU32<4>;
|
||||||
|
// 100 is good enough when the main currency has 12 decimals.
|
||||||
|
type MintMinLiquidity = ConstU64<100>;
|
||||||
|
|
||||||
|
type Balance = u64;
|
||||||
|
type HigherPrecisionBalance = u128;
|
||||||
|
|
||||||
|
type MultiCoinId = Coin;
|
||||||
|
type MultiCoinIdConverter = CoinConverter;
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime-benchmarks")]
|
||||||
|
type BenchmarkHelper = ();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
|
||||||
|
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||||
|
|
||||||
|
let accounts: Vec<Public> = vec![
|
||||||
|
system_address(b"account1").into(),
|
||||||
|
system_address(b"account2").into(),
|
||||||
|
system_address(b"account3").into(),
|
||||||
|
system_address(b"account4").into(),
|
||||||
|
];
|
||||||
|
coins::GenesisConfig::<Test> {
|
||||||
|
accounts: accounts
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| (a, Balance { coin: Coin::Serai, amount: Amount(1 << 60) }))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
.assimilate_storage(&mut t)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut ext = sp_io::TestExternalities::new(t);
|
||||||
|
ext.execute_with(|| System::set_block_number(1));
|
||||||
|
ext
|
||||||
|
}
|
1411
substrate/dex/pallet/src/tests.rs
Normal file
1411
substrate/dex/pallet/src/tests.rs
Normal file
File diff suppressed because it is too large
Load diff
259
substrate/dex/pallet/src/weights.rs
Normal file
259
substrate/dex/pallet/src/weights.rs
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
// This file was originally:
|
||||||
|
|
||||||
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
//! Autogenerated weights for pallet_coin_conversion
|
||||||
|
//!
|
||||||
|
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
|
||||||
|
//! DATE: 2023-07-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||||
|
//! WORST CASE MAP SIZE: `1000000`
|
||||||
|
//! HOSTNAME: `runner-gghbxkbs-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
|
||||||
|
//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024`
|
||||||
|
|
||||||
|
// Executed Command:
|
||||||
|
// target/production/substrate
|
||||||
|
// benchmark
|
||||||
|
// pallet
|
||||||
|
// --steps=50
|
||||||
|
// --repeat=20
|
||||||
|
// --extrinsic=*
|
||||||
|
// --wasm-execution=compiled
|
||||||
|
// --heap-pages=4096
|
||||||
|
// --json-file=/builds/parity/mirrors/substrate/.git/.artifacts/bench.json
|
||||||
|
// --pallet=pallet_coin_conversion
|
||||||
|
// --chain=dev
|
||||||
|
// --header=./HEADER-APACHE2
|
||||||
|
// --output=./frame/coin-conversion/src/weights.rs
|
||||||
|
// --template=./.maintain/frame-weight-template.hbs
|
||||||
|
|
||||||
|
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||||
|
#![allow(unused_parens)]
|
||||||
|
#![allow(unused_imports)]
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
|
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
/// Weight functions needed for pallet_coin_conversion.
|
||||||
|
pub trait WeightInfo {
|
||||||
|
fn create_pool() -> Weight;
|
||||||
|
fn add_liquidity() -> Weight;
|
||||||
|
fn remove_liquidity() -> Weight;
|
||||||
|
fn swap_exact_tokens_for_tokens() -> Weight;
|
||||||
|
fn swap_tokens_for_exact_tokens() -> Weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Weights for pallet_coin_conversion using the Substrate node and recommended hardware.
|
||||||
|
pub struct SubstrateWeight<T>(PhantomData<T>);
|
||||||
|
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||||
|
/// Storage: `CoinConversion::Pools` (r:1 w:1)
|
||||||
|
/// Proof: `CoinConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `System::Account` (r:2 w:2)
|
||||||
|
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Account` (r:1 w:1)
|
||||||
|
/// Proof: `Coins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Coin` (r:1 w:1)
|
||||||
|
/// Proof: `Coins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `CoinConversion::NextPoolCoinId` (r:1 w:1)
|
||||||
|
/// Proof: `CoinConversion::NextPoolCoinId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `PoolCoins::Coin` (r:1 w:1)
|
||||||
|
/// Proof: `PoolCoins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `PoolCoins::Account` (r:1 w:1)
|
||||||
|
/// Proof: `PoolCoins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
fn create_pool() -> Weight {
|
||||||
|
// Proof Size summary in bytes:
|
||||||
|
// Measured: `729`
|
||||||
|
// Estimated: `6196`
|
||||||
|
// Minimum execution time: 131_688_000 picoseconds.
|
||||||
|
Weight::from_parts(134_092_000, 6196)
|
||||||
|
.saturating_add(T::DbWeight::get().reads(8_u64))
|
||||||
|
.saturating_add(T::DbWeight::get().writes(8_u64))
|
||||||
|
}
|
||||||
|
/// Storage: `CoinConversion::Pools` (r:1 w:0)
|
||||||
|
/// Proof: `CoinConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `System::Account` (r:1 w:1)
|
||||||
|
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Coin` (r:1 w:1)
|
||||||
|
/// Proof: `Coins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Account` (r:2 w:2)
|
||||||
|
/// Proof: `Coins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `PoolCoins::Coin` (r:1 w:1)
|
||||||
|
/// Proof: `PoolCoins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `PoolCoins::Account` (r:2 w:2)
|
||||||
|
/// Proof: `PoolCoins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
fn add_liquidity() -> Weight {
|
||||||
|
// Proof Size summary in bytes:
|
||||||
|
// Measured: `1382`
|
||||||
|
// Estimated: `6208`
|
||||||
|
// Minimum execution time: 157_310_000 picoseconds.
|
||||||
|
Weight::from_parts(161_547_000, 6208)
|
||||||
|
.saturating_add(T::DbWeight::get().reads(8_u64))
|
||||||
|
.saturating_add(T::DbWeight::get().writes(7_u64))
|
||||||
|
}
|
||||||
|
/// Storage: `CoinConversion::Pools` (r:1 w:0)
|
||||||
|
/// Proof: `CoinConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `System::Account` (r:1 w:1)
|
||||||
|
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Coin` (r:1 w:1)
|
||||||
|
/// Proof: `Coins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Account` (r:2 w:2)
|
||||||
|
/// Proof: `Coins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `PoolCoins::Coin` (r:1 w:1)
|
||||||
|
/// Proof: `PoolCoins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `PoolCoins::Account` (r:1 w:1)
|
||||||
|
/// Proof: `PoolCoins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
fn remove_liquidity() -> Weight {
|
||||||
|
// Proof Size summary in bytes:
|
||||||
|
// Measured: `1371`
|
||||||
|
// Estimated: `6208`
|
||||||
|
// Minimum execution time: 142_769_000 picoseconds.
|
||||||
|
Weight::from_parts(145_139_000, 6208)
|
||||||
|
.saturating_add(T::DbWeight::get().reads(7_u64))
|
||||||
|
.saturating_add(T::DbWeight::get().writes(6_u64))
|
||||||
|
}
|
||||||
|
/// Storage: `System::Account` (r:1 w:1)
|
||||||
|
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Coin` (r:3 w:3)
|
||||||
|
/// Proof: `Coins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Account` (r:6 w:6)
|
||||||
|
/// Proof: `Coins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
fn swap_exact_tokens_for_tokens() -> Weight {
|
||||||
|
// Proof Size summary in bytes:
|
||||||
|
// Measured: `1738`
|
||||||
|
// Estimated: `16644`
|
||||||
|
// Minimum execution time: 213_186_000 picoseconds.
|
||||||
|
Weight::from_parts(217_471_000, 16644)
|
||||||
|
.saturating_add(T::DbWeight::get().reads(10_u64))
|
||||||
|
.saturating_add(T::DbWeight::get().writes(10_u64))
|
||||||
|
}
|
||||||
|
/// Storage: `Coins::Coin` (r:3 w:3)
|
||||||
|
/// Proof: `Coins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Account` (r:6 w:6)
|
||||||
|
/// Proof: `Coins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `System::Account` (r:1 w:1)
|
||||||
|
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||||
|
fn swap_tokens_for_exact_tokens() -> Weight {
|
||||||
|
// Proof Size summary in bytes:
|
||||||
|
// Measured: `1738`
|
||||||
|
// Estimated: `16644`
|
||||||
|
// Minimum execution time: 213_793_000 picoseconds.
|
||||||
|
Weight::from_parts(218_584_000, 16644)
|
||||||
|
.saturating_add(T::DbWeight::get().reads(10_u64))
|
||||||
|
.saturating_add(T::DbWeight::get().writes(10_u64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For backwards compatibility and tests.
|
||||||
|
impl WeightInfo for () {
|
||||||
|
/// Storage: `CoinConversion::Pools` (r:1 w:1)
|
||||||
|
/// Proof: `CoinConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `System::Account` (r:2 w:2)
|
||||||
|
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Account` (r:1 w:1)
|
||||||
|
/// Proof: `Coins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Coin` (r:1 w:1)
|
||||||
|
/// Proof: `Coins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `CoinConversion::NextPoolCoinId` (r:1 w:1)
|
||||||
|
/// Proof: `CoinConversion::NextPoolCoinId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `PoolCoins::Coin` (r:1 w:1)
|
||||||
|
/// Proof: `PoolCoins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `PoolCoins::Account` (r:1 w:1)
|
||||||
|
/// Proof: `PoolCoins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
fn create_pool() -> Weight {
|
||||||
|
// Proof Size summary in bytes:
|
||||||
|
// Measured: `729`
|
||||||
|
// Estimated: `6196`
|
||||||
|
// Minimum execution time: 131_688_000 picoseconds.
|
||||||
|
Weight::from_parts(134_092_000, 6196)
|
||||||
|
.saturating_add(RocksDbWeight::get().reads(8_u64))
|
||||||
|
.saturating_add(RocksDbWeight::get().writes(8_u64))
|
||||||
|
}
|
||||||
|
/// Storage: `CoinConversion::Pools` (r:1 w:0)
|
||||||
|
/// Proof: `CoinConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `System::Account` (r:1 w:1)
|
||||||
|
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Coin` (r:1 w:1)
|
||||||
|
/// Proof: `Coins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Account` (r:2 w:2)
|
||||||
|
/// Proof: `Coins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `PoolCoins::Coin` (r:1 w:1)
|
||||||
|
/// Proof: `PoolCoins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `PoolCoins::Account` (r:2 w:2)
|
||||||
|
/// Proof: `PoolCoins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
fn add_liquidity() -> Weight {
|
||||||
|
// Proof Size summary in bytes:
|
||||||
|
// Measured: `1382`
|
||||||
|
// Estimated: `6208`
|
||||||
|
// Minimum execution time: 157_310_000 picoseconds.
|
||||||
|
Weight::from_parts(161_547_000, 6208)
|
||||||
|
.saturating_add(RocksDbWeight::get().reads(8_u64))
|
||||||
|
.saturating_add(RocksDbWeight::get().writes(7_u64))
|
||||||
|
}
|
||||||
|
/// Storage: `CoinConversion::Pools` (r:1 w:0)
|
||||||
|
/// Proof: `CoinConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `System::Account` (r:1 w:1)
|
||||||
|
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Coin` (r:1 w:1)
|
||||||
|
/// Proof: `Coins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Account` (r:2 w:2)
|
||||||
|
/// Proof: `Coins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `PoolCoins::Coin` (r:1 w:1)
|
||||||
|
/// Proof: `PoolCoins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `PoolCoins::Account` (r:1 w:1)
|
||||||
|
/// Proof: `PoolCoins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
fn remove_liquidity() -> Weight {
|
||||||
|
// Proof Size summary in bytes:
|
||||||
|
// Measured: `1371`
|
||||||
|
// Estimated: `6208`
|
||||||
|
// Minimum execution time: 142_769_000 picoseconds.
|
||||||
|
Weight::from_parts(145_139_000, 6208)
|
||||||
|
.saturating_add(RocksDbWeight::get().reads(7_u64))
|
||||||
|
.saturating_add(RocksDbWeight::get().writes(6_u64))
|
||||||
|
}
|
||||||
|
/// Storage: `System::Account` (r:1 w:1)
|
||||||
|
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Coin` (r:3 w:3)
|
||||||
|
/// Proof: `Coins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Account` (r:6 w:6)
|
||||||
|
/// Proof: `Coins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
fn swap_exact_tokens_for_tokens() -> Weight {
|
||||||
|
// Proof Size summary in bytes:
|
||||||
|
// Measured: `1738`
|
||||||
|
// Estimated: `16644`
|
||||||
|
// Minimum execution time: 213_186_000 picoseconds.
|
||||||
|
Weight::from_parts(217_471_000, 16644)
|
||||||
|
.saturating_add(RocksDbWeight::get().reads(10_u64))
|
||||||
|
.saturating_add(RocksDbWeight::get().writes(10_u64))
|
||||||
|
}
|
||||||
|
/// Storage: `Coins::Coin` (r:3 w:3)
|
||||||
|
/// Proof: `Coins::Coin` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `Coins::Account` (r:6 w:6)
|
||||||
|
/// Proof: `Coins::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||||
|
/// Storage: `System::Account` (r:1 w:1)
|
||||||
|
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||||
|
fn swap_tokens_for_exact_tokens() -> Weight {
|
||||||
|
// Proof Size summary in bytes:
|
||||||
|
// Measured: `1738`
|
||||||
|
// Estimated: `16644`
|
||||||
|
// Minimum execution time: 213_793_000 picoseconds.
|
||||||
|
Weight::from_parts(218_584_000, 16644)
|
||||||
|
.saturating_add(RocksDbWeight::get().reads(10_u64))
|
||||||
|
.saturating_add(RocksDbWeight::get().writes(10_u64))
|
||||||
|
}
|
||||||
|
}
|
44
substrate/dex/primitives/Cargo.toml
Normal file
44
substrate/dex/primitives/Cargo.toml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
[package]
|
||||||
|
name = "serai-dex-primitives"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Dex pallet primitives"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/dex/primitives"
|
||||||
|
authors = ["Parity Technologies <admin@parity.io>, Akil Demir <aeg_asd@hotmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
targets = ["x86_64-unknown-linux-gnu"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
|
||||||
|
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
|
||||||
|
|
||||||
|
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
frame-benchmarking = { git = "https://github.com/serai-dex/substrate", default-features = false, optional = true }
|
||||||
|
|
||||||
|
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
|
serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = [ "std" ]
|
||||||
|
std = [
|
||||||
|
"codec/std",
|
||||||
|
"scale-info/std",
|
||||||
|
|
||||||
|
"frame-support/std",
|
||||||
|
"frame-benchmarking?/std",
|
||||||
|
|
||||||
|
"serai-primitives/std",
|
||||||
|
|
||||||
|
"sp-runtime/std",
|
||||||
|
"sp-std/std",
|
||||||
|
]
|
||||||
|
runtime-benchmarks = [
|
||||||
|
"frame-benchmarking/runtime-benchmarks",
|
||||||
|
"frame-support/runtime-benchmarks",
|
||||||
|
]
|
15
substrate/dex/primitives/LICENSE-AGPL3
Normal file
15
substrate/dex/primitives/LICENSE-AGPL3
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
AGPL-3.0-only license
|
||||||
|
|
||||||
|
Copyright (c) 2023 Luke Parker
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License Version 3 as
|
||||||
|
published by the Free Software Foundation.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
211
substrate/dex/primitives/LICENSE-APACHE2
Normal file
211
substrate/dex/primitives/LICENSE-APACHE2
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
NOTE
|
||||||
|
|
||||||
|
Individual files contain the following tag instead of the full license
|
||||||
|
text.
|
||||||
|
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
This enables machine processing of license information based on the SPDX
|
||||||
|
License Identifiers that are here available: http://spdx.org/licenses/
|
221
substrate/dex/primitives/src/lib.rs
Normal file
221
substrate/dex/primitives/src/lib.rs
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
// This file was originally:
|
||||||
|
|
||||||
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
|
use codec::{Decode, Encode, MaxEncodedLen};
|
||||||
|
use scale_info::TypeInfo;
|
||||||
|
|
||||||
|
use sp_runtime::DispatchError;
|
||||||
|
use sp_std::vec::Vec;
|
||||||
|
|
||||||
|
use frame_support::traits::tokens::{Balance, AssetId as CoinId};
|
||||||
|
|
||||||
|
use serai_primitives::Coin;
|
||||||
|
|
||||||
|
/// Stores the lp_token coin id a particular pool has been assigned.
|
||||||
|
#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)]
|
||||||
|
pub struct PoolInfo<PoolCoinId> {
|
||||||
|
/// Liquidity pool coin
|
||||||
|
pub lp_token: PoolCoinId,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait that converts between a MultiCoinId and either the native currency or an CoinId.
|
||||||
|
pub trait MultiCoinIdConverter<MultiCoinId, CoinId> {
|
||||||
|
/// Returns the MultiCoinId representing the native currency of the chain.
|
||||||
|
fn get_native() -> MultiCoinId;
|
||||||
|
|
||||||
|
/// Returns true if the given MultiCoinId is the native currency.
|
||||||
|
fn is_native(coin: &MultiCoinId) -> bool;
|
||||||
|
|
||||||
|
/// If it's not native, returns the CoinId for the given MultiCoinId.
|
||||||
|
fn try_convert(coin: &MultiCoinId) -> MultiCoinIdConversionResult<MultiCoinId, CoinId>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of `MultiCoinIdConverter::try_convert`.
|
||||||
|
#[cfg_attr(feature = "std", derive(PartialEq, Debug))]
|
||||||
|
pub enum MultiCoinIdConversionResult<MultiCoinId, CoinId> {
|
||||||
|
/// Input coin is successfully converted. Means that converted coin is supported.
|
||||||
|
Converted(CoinId),
|
||||||
|
/// Means that input coin is the chain's native coin, if it has one, so no conversion (see
|
||||||
|
/// `MultiCoinIdConverter::get_native`).
|
||||||
|
Native,
|
||||||
|
/// Means input coin is not supported for pool.
|
||||||
|
Unsupported(MultiCoinId),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Benchmark Helper
|
||||||
|
#[cfg(feature = "runtime-benchmarks")]
|
||||||
|
pub trait BenchmarkHelper<CoinId, MultiCoinId> {
|
||||||
|
/// Returns an `CoinId` from a given integer.
|
||||||
|
fn coin_id(coin_id: u32) -> CoinId;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime-benchmarks")]
|
||||||
|
mod runtime_benchmarks {
|
||||||
|
use super::*;
|
||||||
|
use serai_primitives::COINS;
|
||||||
|
impl BenchmarkHelper<Coin, Coin> for () {
|
||||||
|
fn coin_id(coin_id: u32) -> Coin {
|
||||||
|
// we shift id 1 unit to the left, since id 0 is the native coin.
|
||||||
|
COINS[(usize::try_from(coin_id).unwrap() % COINS.len()) + 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for providing methods to swap between the various coin classes.
|
||||||
|
pub trait Swap<AccountId, Balance, MultiCoinId> {
|
||||||
|
/// Swap exactly `amount_in` of coin `path[0]` for coin `path[1]`.
|
||||||
|
/// If an `amount_out_min` is specified, it will return an error if it is unable to acquire
|
||||||
|
/// the amount desired.
|
||||||
|
///
|
||||||
|
/// Withdraws the `path[0]` coin from `sender`, deposits the `path[1]` coin to `send_to`,
|
||||||
|
///
|
||||||
|
/// If successful, returns the amount of `path[1]` acquired for the `amount_in`.
|
||||||
|
fn swap_exact_tokens_for_tokens(
|
||||||
|
sender: AccountId,
|
||||||
|
path: Vec<MultiCoinId>,
|
||||||
|
amount_in: Balance,
|
||||||
|
amount_out_min: Option<Balance>,
|
||||||
|
send_to: AccountId,
|
||||||
|
) -> Result<Balance, DispatchError>;
|
||||||
|
|
||||||
|
/// Take the `path[0]` coin and swap some amount for `amount_out` of the `path[1]`. If an
|
||||||
|
/// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be
|
||||||
|
/// too costly.
|
||||||
|
///
|
||||||
|
/// Withdraws `path[0]` coin from `sender`, deposits `path[1]` coin to `send_to`,
|
||||||
|
///
|
||||||
|
/// If successful returns the amount of the `path[0]` taken to provide `path[1]`.
|
||||||
|
fn swap_tokens_for_exact_tokens(
|
||||||
|
sender: AccountId,
|
||||||
|
path: Vec<MultiCoinId>,
|
||||||
|
amount_out: Balance,
|
||||||
|
amount_in_max: Option<Balance>,
|
||||||
|
send_to: AccountId,
|
||||||
|
) -> Result<Balance, DispatchError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Sized should be there?
|
||||||
|
/// Native coin trait for Dex pallet.
|
||||||
|
pub trait Currency<AccountId>: Sized {
|
||||||
|
/// Balance of an Account.
|
||||||
|
type Balance: Balance;
|
||||||
|
|
||||||
|
/// Returns the balance of an account.
|
||||||
|
fn balance(of: &AccountId) -> Self::Balance;
|
||||||
|
|
||||||
|
/// Returns the minimum allowed balance of an account
|
||||||
|
fn minimum_balance() -> Self::Balance;
|
||||||
|
|
||||||
|
/// Transfers the given `amount` from `from` to `to`.
|
||||||
|
fn transfer(
|
||||||
|
from: &AccountId,
|
||||||
|
to: &AccountId,
|
||||||
|
amount: Self::Balance,
|
||||||
|
) -> Result<Self::Balance, DispatchError>;
|
||||||
|
|
||||||
|
/// mints the given `amount` into `to`.
|
||||||
|
fn mint(to: &AccountId, amount: Self::Balance) -> Result<Self::Balance, DispatchError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// External coin trait for Dex pallet.
|
||||||
|
pub trait Coins<AccountId>: Sized {
|
||||||
|
/// Balance of an Account.
|
||||||
|
type Balance: Balance;
|
||||||
|
|
||||||
|
/// Coin identifier.
|
||||||
|
type CoinId: CoinId;
|
||||||
|
|
||||||
|
/// Returns the balance of an account.
|
||||||
|
fn balance(coin: Self::CoinId, of: &AccountId) -> Self::Balance;
|
||||||
|
|
||||||
|
/// Returns the minimum allowed balance of an account
|
||||||
|
fn minimum_balance(coin: Self::CoinId) -> Self::Balance;
|
||||||
|
|
||||||
|
/// Transfers the given `amount` from `from` to `to`.
|
||||||
|
fn transfer(
|
||||||
|
coin: Self::CoinId,
|
||||||
|
from: &AccountId,
|
||||||
|
to: &AccountId,
|
||||||
|
amount: Self::Balance,
|
||||||
|
) -> Result<Self::Balance, DispatchError>;
|
||||||
|
|
||||||
|
/// mints the given `amount` of `coin` into `to`.
|
||||||
|
fn mint(
|
||||||
|
coin: Self::CoinId,
|
||||||
|
to: &AccountId,
|
||||||
|
amount: Self::Balance,
|
||||||
|
) -> Result<Self::Balance, DispatchError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Liquidity tokens trait for Dex pallet.
|
||||||
|
pub trait LiquidityTokens<AccountId>: Sized {
|
||||||
|
/// Amount type.
|
||||||
|
type Balance: Balance;
|
||||||
|
|
||||||
|
/// Coin identifier.
|
||||||
|
type CoinId: CoinId;
|
||||||
|
|
||||||
|
/// Returns the `token` balance of and account.
|
||||||
|
fn balance(token: Self::CoinId, of: &AccountId) -> Self::Balance;
|
||||||
|
|
||||||
|
/// Mints `amount` to `to`.
|
||||||
|
fn mint_into(
|
||||||
|
token: Self::CoinId,
|
||||||
|
to: &AccountId,
|
||||||
|
amount: Self::Balance,
|
||||||
|
) -> Result<Self::Balance, DispatchError>;
|
||||||
|
|
||||||
|
/// Burns `amount` from `from`.
|
||||||
|
fn burn_from(
|
||||||
|
token: Self::CoinId,
|
||||||
|
from: &AccountId,
|
||||||
|
amount: Self::Balance,
|
||||||
|
) -> Result<Self::Balance, DispatchError>;
|
||||||
|
|
||||||
|
/// Returns total supply for `token`.
|
||||||
|
fn total_issuance(token: Self::CoinId) -> Self::Balance;
|
||||||
|
|
||||||
|
/// Returns an iterator of the collections in existence.
|
||||||
|
fn coin_ids() -> Vec<Self::CoinId>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CoinConverter;
|
||||||
|
impl MultiCoinIdConverter<Coin, Coin> for CoinConverter {
|
||||||
|
/// Returns the MultiCoinId representing the native currency of the chain.
|
||||||
|
fn get_native() -> Coin {
|
||||||
|
Coin::native()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the given MultiCoinId is the native currency.
|
||||||
|
fn is_native(coin: &Coin) -> bool {
|
||||||
|
coin.is_native()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If it's not native, returns the CoinId for the given MultiCoinId.
|
||||||
|
fn try_convert(coin: &Coin) -> MultiCoinIdConversionResult<Coin, Coin> {
|
||||||
|
if coin.is_native() {
|
||||||
|
MultiCoinIdConversionResult::Native
|
||||||
|
} else {
|
||||||
|
MultiCoinIdConversionResult::Converted(*coin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,10 +17,11 @@ thiserror = { version = "1", optional = true }
|
||||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "max-encoded-len"] }
|
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "max-encoded-len"] }
|
||||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||||
|
|
||||||
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
sp-io = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
|
||||||
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
sp-io = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
@ -29,6 +30,7 @@ serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
in-instructions-primitives = { package = "serai-in-instructions-primitives", path = "../primitives", default-features = false }
|
in-instructions-primitives = { package = "serai-in-instructions-primitives", path = "../primitives", default-features = false }
|
||||||
|
|
||||||
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
|
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/pallet", default-features = false }
|
||||||
|
dex-pallet = { package = "serai-dex-pallet", path = "../../dex/pallet", default-features = false }
|
||||||
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false }
|
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../../validator-sets/pallet", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
@ -38,10 +40,11 @@ std = [
|
||||||
"scale/std",
|
"scale/std",
|
||||||
"scale-info/std",
|
"scale-info/std",
|
||||||
|
|
||||||
"sp-core/std",
|
"sp-std/std",
|
||||||
"sp-io/std",
|
|
||||||
"sp-application-crypto/std",
|
"sp-application-crypto/std",
|
||||||
|
"sp-io/std",
|
||||||
"sp-runtime/std",
|
"sp-runtime/std",
|
||||||
|
"sp-core/std",
|
||||||
|
|
||||||
"frame-system/std",
|
"frame-system/std",
|
||||||
"frame-support/std",
|
"frame-support/std",
|
||||||
|
@ -50,6 +53,7 @@ std = [
|
||||||
"in-instructions-primitives/std",
|
"in-instructions-primitives/std",
|
||||||
|
|
||||||
"coins-pallet/std",
|
"coins-pallet/std",
|
||||||
|
"dex-pallet/std",
|
||||||
"validator-sets-pallet/std",
|
"validator-sets-pallet/std",
|
||||||
]
|
]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|
|
@ -23,14 +23,21 @@ pub enum PalletError {
|
||||||
|
|
||||||
#[frame_support::pallet]
|
#[frame_support::pallet]
|
||||||
pub mod pallet {
|
pub mod pallet {
|
||||||
|
use sp_std::vec;
|
||||||
use sp_application_crypto::RuntimePublic;
|
use sp_application_crypto::RuntimePublic;
|
||||||
use sp_runtime::traits::Zero;
|
use sp_runtime::traits::Zero;
|
||||||
use sp_core::sr25519::Public;
|
use sp_core::sr25519::Public;
|
||||||
|
|
||||||
use frame_support::pallet_prelude::*;
|
use serai_primitives::{Coin, SubstrateAmount, Amount, Balance};
|
||||||
use frame_system::pallet_prelude::*;
|
|
||||||
|
|
||||||
use coins_pallet::{Config as CoinsConfig, Pallet as Coins};
|
use frame_support::pallet_prelude::*;
|
||||||
|
use frame_system::{pallet_prelude::*, RawOrigin};
|
||||||
|
|
||||||
|
use coins_pallet::{
|
||||||
|
Config as CoinsConfig, Pallet as Coins,
|
||||||
|
primitives::{OutInstruction, OutInstructionWithBalance},
|
||||||
|
};
|
||||||
|
use dex_pallet::{Config as DexConfig, Pallet as Dex};
|
||||||
use validator_sets_pallet::{
|
use validator_sets_pallet::{
|
||||||
primitives::{Session, ValidatorSet},
|
primitives::{Session, ValidatorSet},
|
||||||
Config as ValidatorSetsConfig, Pallet as ValidatorSets,
|
Config as ValidatorSetsConfig, Pallet as ValidatorSets,
|
||||||
|
@ -39,7 +46,12 @@ pub mod pallet {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[pallet::config]
|
#[pallet::config]
|
||||||
pub trait Config: frame_system::Config + ValidatorSetsConfig + CoinsConfig {
|
pub trait Config:
|
||||||
|
frame_system::Config
|
||||||
|
+ CoinsConfig
|
||||||
|
+ DexConfig<MultiCoinId = Coin, CoinBalance = SubstrateAmount>
|
||||||
|
+ ValidatorSetsConfig
|
||||||
|
{
|
||||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +62,12 @@ pub mod pallet {
|
||||||
InstructionFailure { network: NetworkId, id: u32, index: u32 },
|
InstructionFailure { network: NetworkId, id: u32, index: u32 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pallet::error]
|
||||||
|
pub enum Error<T> {
|
||||||
|
/// Coin and OutAddress types don't match.
|
||||||
|
InvalidAddressForCoin,
|
||||||
|
}
|
||||||
|
|
||||||
#[pallet::pallet]
|
#[pallet::pallet]
|
||||||
pub struct Pallet<T>(PhantomData<T>);
|
pub struct Pallet<T>(PhantomData<T>);
|
||||||
|
|
||||||
|
@ -79,7 +97,120 @@ pub mod pallet {
|
||||||
InInstruction::Transfer(address) => {
|
InInstruction::Transfer(address) => {
|
||||||
Coins::<T>::mint(address.into(), instruction.balance)?;
|
Coins::<T>::mint(address.into(), instruction.balance)?;
|
||||||
}
|
}
|
||||||
_ => panic!("unsupported instruction"),
|
InInstruction::Dex(call) => {
|
||||||
|
// This will only be initiated by external chain transactions. That is why we only need
|
||||||
|
// add liquidity and swaps. Other functionalities (such as remove_liq, etc) will be
|
||||||
|
// called directly from Serai with a native transaction.
|
||||||
|
match call {
|
||||||
|
DexCall::SwapAndAddLiquidity(address) => {
|
||||||
|
let origin = RawOrigin::Signed(IN_INSTRUCTION_EXECUTOR.into());
|
||||||
|
let coin = instruction.balance.coin;
|
||||||
|
|
||||||
|
// mint the given coin on the account
|
||||||
|
Coins::<T>::mint(IN_INSTRUCTION_EXECUTOR.into(), instruction.balance)?;
|
||||||
|
|
||||||
|
// swap half of it for SRI
|
||||||
|
let half = instruction.balance.amount.0 / 2;
|
||||||
|
let path = BoundedVec::try_from(vec![coin, Coin::Serai]).unwrap();
|
||||||
|
Dex::<T>::swap_exact_tokens_for_tokens(
|
||||||
|
origin.clone().into(),
|
||||||
|
path,
|
||||||
|
half,
|
||||||
|
1, // minimum out, so we accept whatever we get.
|
||||||
|
IN_INSTRUCTION_EXECUTOR.into(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// get how much we got for our swap
|
||||||
|
let sri_amount = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), Coin::Serai).0;
|
||||||
|
|
||||||
|
// add liquidity
|
||||||
|
Dex::<T>::add_liquidity(
|
||||||
|
origin.clone().into(),
|
||||||
|
coin,
|
||||||
|
Coin::Serai,
|
||||||
|
half,
|
||||||
|
sri_amount,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
address.into(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// Let's send the leftovers back to user for now.
|
||||||
|
let coin_balance = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), coin);
|
||||||
|
let sri_balance = Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), Coin::Serai);
|
||||||
|
if coin_balance != Amount(0) {
|
||||||
|
Coins::<T>::transfer_internal(
|
||||||
|
IN_INSTRUCTION_EXECUTOR.into(),
|
||||||
|
address.into(),
|
||||||
|
Balance { coin, amount: coin_balance },
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
if sri_balance != Amount(0) {
|
||||||
|
Coins::<T>::transfer_internal(
|
||||||
|
IN_INSTRUCTION_EXECUTOR.into(),
|
||||||
|
address.into(),
|
||||||
|
Balance { coin: Coin::Serai, amount: sri_balance },
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DexCall::Swap(out_balance, out_address) => {
|
||||||
|
let send_to_external = !out_address.is_native();
|
||||||
|
let native_coin = out_balance.coin.is_native();
|
||||||
|
|
||||||
|
// we can't send native coin to external chain
|
||||||
|
if native_coin && send_to_external {
|
||||||
|
Err(Error::<T>::InvalidAddressForCoin)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mint the given coin on our account
|
||||||
|
Coins::<T>::mint(IN_INSTRUCTION_EXECUTOR.into(), instruction.balance)?;
|
||||||
|
|
||||||
|
// get the path
|
||||||
|
let mut path = vec![instruction.balance.coin, Coin::Serai];
|
||||||
|
if !native_coin {
|
||||||
|
path.push(out_balance.coin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the swap address
|
||||||
|
// if the address is internal, we can directly swap to it. if not, we swap to
|
||||||
|
// ourselves and burn the coins to send them back on the external chain.
|
||||||
|
let send_to = if send_to_external {
|
||||||
|
IN_INSTRUCTION_EXECUTOR
|
||||||
|
} else {
|
||||||
|
out_address.clone().as_native().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
// do the swap
|
||||||
|
let origin = RawOrigin::Signed(IN_INSTRUCTION_EXECUTOR.into());
|
||||||
|
Dex::<T>::swap_exact_tokens_for_tokens(
|
||||||
|
origin.into(),
|
||||||
|
BoundedVec::try_from(path).unwrap(),
|
||||||
|
instruction.balance.amount.0,
|
||||||
|
out_balance.amount.0,
|
||||||
|
send_to.into(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// burn the received coins so that they sent back to the user
|
||||||
|
// if it is requested to an external address.
|
||||||
|
if send_to_external {
|
||||||
|
// see how much we got
|
||||||
|
let coin_balance =
|
||||||
|
Coins::<T>::balance(IN_INSTRUCTION_EXECUTOR.into(), out_balance.coin);
|
||||||
|
let instruction = OutInstructionWithBalance {
|
||||||
|
instruction: OutInstruction {
|
||||||
|
address: out_address.as_external().unwrap(),
|
||||||
|
// TODO: Properly pass data. Replace address with an OutInstruction entirely?
|
||||||
|
data: None,
|
||||||
|
},
|
||||||
|
balance: Balance { coin: out_balance.coin, amount: coin_balance },
|
||||||
|
};
|
||||||
|
Coins::<T>::burn_non_sri(IN_INSTRUCTION_EXECUTOR.into(), instruction)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ serde = { version = "1", default-features = false, features = ["derive", "alloc"
|
||||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||||
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||||
|
|
||||||
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
|
||||||
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
sp-application-crypto = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
serai-primitives = { path = "../../primitives", default-features = false }
|
serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
|
@ -35,6 +35,7 @@ std = [
|
||||||
"scale-info/std",
|
"scale-info/std",
|
||||||
|
|
||||||
"sp-std/std",
|
"sp-std/std",
|
||||||
|
"sp-application-crypto/std",
|
||||||
"sp-runtime/std",
|
"sp-runtime/std",
|
||||||
|
|
||||||
"serai-primitives/std",
|
"serai-primitives/std",
|
||||||
|
|
|
@ -16,20 +16,65 @@ use sp_application_crypto::sr25519::Signature;
|
||||||
use sp_std::vec::Vec;
|
use sp_std::vec::Vec;
|
||||||
use sp_runtime::RuntimeDebug;
|
use sp_runtime::RuntimeDebug;
|
||||||
|
|
||||||
use serai_primitives::{BlockHash, Balance, NetworkId, SeraiAddress, ExternalAddress, Data};
|
#[rustfmt::skip]
|
||||||
|
use serai_primitives::{BlockHash, Balance, NetworkId, SeraiAddress, ExternalAddress, system_address};
|
||||||
|
|
||||||
mod shorthand;
|
mod shorthand;
|
||||||
pub use shorthand::*;
|
pub use shorthand::*;
|
||||||
|
|
||||||
pub const MAX_BATCH_SIZE: usize = 25_000; // ~25kb
|
pub const MAX_BATCH_SIZE: usize = 25_000; // ~25kb
|
||||||
|
|
||||||
|
// This is the account which will be the origin for any dispatched `InInstruction`s.
|
||||||
|
pub const IN_INSTRUCTION_EXECUTOR: SeraiAddress = system_address(b"InInstructions-executor");
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Encode, Decode, MaxEncodedLen, TypeInfo,
|
||||||
|
)]
|
||||||
|
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||||
|
pub enum OutAddress {
|
||||||
|
Serai(SeraiAddress),
|
||||||
|
External(ExternalAddress),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutAddress {
|
||||||
|
pub fn is_native(&self) -> bool {
|
||||||
|
matches!(self, Self::Serai(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_native(self) -> Option<SeraiAddress> {
|
||||||
|
match self {
|
||||||
|
Self::Serai(addr) => Some(addr),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_external(self) -> Option<ExternalAddress> {
|
||||||
|
match self {
|
||||||
|
Self::External(addr) => Some(addr),
|
||||||
|
Self::Serai(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(
|
||||||
|
Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Encode, Decode, MaxEncodedLen, TypeInfo,
|
||||||
|
)]
|
||||||
|
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||||
|
pub enum DexCall {
|
||||||
|
// address to send the lp tokens to
|
||||||
|
// TODO: Update this per documentation/Shorthand
|
||||||
|
SwapAndAddLiquidity(SeraiAddress),
|
||||||
|
// minimum out balance and out address
|
||||||
|
Swap(Balance, OutAddress),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Encode, Decode, MaxEncodedLen, TypeInfo,
|
Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Encode, Decode, MaxEncodedLen, TypeInfo,
|
||||||
)]
|
)]
|
||||||
#[cfg_attr(feature = "std", derive(Zeroize))]
|
#[cfg_attr(feature = "std", derive(Zeroize))]
|
||||||
pub enum InInstruction {
|
pub enum InInstruction {
|
||||||
Transfer(SeraiAddress),
|
Transfer(SeraiAddress),
|
||||||
Dex(Data),
|
Dex(DexCall),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub enum Shorthand {
|
||||||
minimum: Amount,
|
minimum: Amount,
|
||||||
out: OutInstruction,
|
out: OutInstruction,
|
||||||
},
|
},
|
||||||
AddLiquidity {
|
SwapAndAddLiquidity {
|
||||||
origin: Option<ExternalAddress>,
|
origin: Option<ExternalAddress>,
|
||||||
minimum: Amount,
|
minimum: Amount,
|
||||||
gas: Amount,
|
gas: Amount,
|
||||||
|
@ -47,7 +47,7 @@ impl TryFrom<Shorthand> for RefundableInInstruction {
|
||||||
Ok(match shorthand {
|
Ok(match shorthand {
|
||||||
Shorthand::Raw(instruction) => instruction,
|
Shorthand::Raw(instruction) => instruction,
|
||||||
Shorthand::Swap { .. } => todo!(),
|
Shorthand::Swap { .. } => todo!(),
|
||||||
Shorthand::AddLiquidity { .. } => todo!(),
|
Shorthand::SwapAndAddLiquidity { .. } => todo!(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
46
substrate/liquidity-tokens/pallet/Cargo.toml
Normal file
46
substrate/liquidity-tokens/pallet/Cargo.toml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
[package]
|
||||||
|
name = "serai-liquidity-tokens-pallet"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "liquidity tokens pallet for Serai"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/liquidity-tokens/pallet"
|
||||||
|
authors = ["Akil Demir <aeg_asd@hotmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
parity-scale-codec = { version = "3", default-features = false, features = ["derive"] }
|
||||||
|
scale-info = { version = "2", default-features = false, features = ["derive"] }
|
||||||
|
|
||||||
|
frame-system = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
|
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
sp-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
|
dex-primitives = { package = "serai-dex-primitives", path = "../../dex/primitives", default-features = false }
|
||||||
|
|
||||||
|
serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
std = [
|
||||||
|
"frame-system/std",
|
||||||
|
"frame-support/std",
|
||||||
|
|
||||||
|
"sp-core/std",
|
||||||
|
"sp-std/std",
|
||||||
|
|
||||||
|
"dex-primitives/std",
|
||||||
|
|
||||||
|
"serai-primitives/std",
|
||||||
|
]
|
||||||
|
|
||||||
|
runtime-benchmarks = [
|
||||||
|
"frame-system/runtime-benchmarks",
|
||||||
|
"frame-support/runtime-benchmarks",
|
||||||
|
]
|
||||||
|
|
||||||
|
default = ["std"]
|
15
substrate/liquidity-tokens/pallet/LICENSE
Normal file
15
substrate/liquidity-tokens/pallet/LICENSE
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
AGPL-3.0-only license
|
||||||
|
|
||||||
|
Copyright (c) 2023 Luke Parker
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License Version 3 as
|
||||||
|
published by the Free Software Foundation.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
152
substrate/liquidity-tokens/pallet/src/lib.rs
Normal file
152
substrate/liquidity-tokens/pallet/src/lib.rs
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
|
#[frame_support::pallet]
|
||||||
|
pub mod pallet {
|
||||||
|
use sp_core::sr25519::Public;
|
||||||
|
use sp_std::vec::Vec;
|
||||||
|
use frame_support::pallet_prelude::*;
|
||||||
|
|
||||||
|
use dex_primitives::LiquidityTokens;
|
||||||
|
use serai_primitives::*;
|
||||||
|
|
||||||
|
#[pallet::config]
|
||||||
|
pub trait Config: frame_system::Config<AccountId = Public> {
|
||||||
|
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pallet::error]
|
||||||
|
pub enum Error<T> {
|
||||||
|
AmountOverflowed,
|
||||||
|
NotEnoughCoins,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pallet::event]
|
||||||
|
#[pallet::generate_deposit(fn deposit_event)]
|
||||||
|
pub enum Event<T: Config> {
|
||||||
|
LtMint { to: Public, token: u32, amount: Amount },
|
||||||
|
LtBurn { from: Public, token: u32, amount: Amount },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pallet::pallet]
|
||||||
|
pub struct Pallet<T>(PhantomData<T>);
|
||||||
|
|
||||||
|
/// The amount of coins each account has.
|
||||||
|
// Identity is used as the second key's hasher due to it being a non-manipulatable fixed-space
|
||||||
|
// ID.
|
||||||
|
#[pallet::storage]
|
||||||
|
#[pallet::getter(fn balances)]
|
||||||
|
pub type Balances<T: Config> = StorageDoubleMap<
|
||||||
|
_,
|
||||||
|
Blake2_128Concat,
|
||||||
|
Public,
|
||||||
|
Blake2_128Concat,
|
||||||
|
u32,
|
||||||
|
SubstrateAmount,
|
||||||
|
OptionQuery,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// The total supply of each coin.
|
||||||
|
// We use Identity type here again due to reasons stated in the Balances Storage.
|
||||||
|
#[pallet::storage]
|
||||||
|
#[pallet::getter(fn supply)]
|
||||||
|
pub type Supply<T: Config> = StorageMap<_, Blake2_128Concat, u32, SubstrateAmount, ValueQuery>;
|
||||||
|
|
||||||
|
// TODO: apis: supply, mint, burn, transfer
|
||||||
|
impl<T: Config> Pallet<T> {
|
||||||
|
/// Returns the balance of a given account for `token`.
|
||||||
|
pub fn balance(token: u32, of: T::AccountId) -> SubstrateAmount {
|
||||||
|
Self::balances(of, token).unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mint `balance` to the given account.
|
||||||
|
///
|
||||||
|
/// Errors if any amount overflows.
|
||||||
|
pub fn mint_into(token: u32, to: Public, amount: SubstrateAmount) -> Result<(), Error<T>> {
|
||||||
|
let balance = Self::balances(to, token).unwrap_or(0);
|
||||||
|
|
||||||
|
// update the balance
|
||||||
|
let new_amount = balance.checked_add(amount).ok_or(Error::<T>::AmountOverflowed)?;
|
||||||
|
|
||||||
|
// save
|
||||||
|
Balances::<T>::set(to, token, Some(new_amount));
|
||||||
|
|
||||||
|
// update the supply
|
||||||
|
let new_supply =
|
||||||
|
Self::supply(token).checked_add(amount).ok_or(Error::<T>::AmountOverflowed)?;
|
||||||
|
Supply::<T>::set(token, new_supply);
|
||||||
|
|
||||||
|
Self::deposit_event(Event::LtMint { to, token, amount: Amount(amount) });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Burn `balance` from the specified account.
|
||||||
|
pub fn burn_from(token: u32, from: Public, amount: SubstrateAmount) -> Result<(), Error<T>> {
|
||||||
|
let balance = Self::balances(from, token);
|
||||||
|
if balance.is_none() {
|
||||||
|
Err(Error::<T>::NotEnoughCoins)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the balance
|
||||||
|
let new_amount = balance.unwrap().checked_sub(amount).ok_or(Error::<T>::NotEnoughCoins)?;
|
||||||
|
|
||||||
|
// save
|
||||||
|
if new_amount == 0 {
|
||||||
|
Balances::<T>::remove(from, token);
|
||||||
|
} else {
|
||||||
|
Balances::<T>::set(from, token, Some(new_amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the supply
|
||||||
|
let new_supply = Self::supply(token).checked_sub(amount).unwrap();
|
||||||
|
if new_supply == 0 {
|
||||||
|
Supply::<T>::remove(token);
|
||||||
|
} else {
|
||||||
|
Supply::<T>::set(token, new_supply);
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::deposit_event(Event::LtBurn { from, token, amount: Amount(amount) });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn total_issuance(token: u32) -> SubstrateAmount {
|
||||||
|
Supply::<T>::get(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Config> LiquidityTokens<T::AccountId> for Pallet<T> {
|
||||||
|
type Balance = SubstrateAmount;
|
||||||
|
type CoinId = u32;
|
||||||
|
|
||||||
|
fn mint_into(
|
||||||
|
token: Self::CoinId,
|
||||||
|
to: &Public,
|
||||||
|
amount: Self::Balance,
|
||||||
|
) -> Result<Self::Balance, DispatchError> {
|
||||||
|
Self::mint_into(token, *to, amount)?;
|
||||||
|
Ok(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn burn_from(
|
||||||
|
token: Self::CoinId,
|
||||||
|
from: &Public,
|
||||||
|
amount: Self::Balance,
|
||||||
|
) -> Result<Self::Balance, DispatchError> {
|
||||||
|
Self::burn_from(token, *from, amount)?;
|
||||||
|
Ok(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn total_issuance(token: Self::CoinId) -> Self::Balance {
|
||||||
|
Self::total_issuance(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn coin_ids() -> Vec<Self::CoinId> {
|
||||||
|
Supply::<T>::iter_keys().collect::<Vec<Self::CoinId>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn balance(token: Self::CoinId, of: &Public) -> Self::Balance {
|
||||||
|
Self::balance(token, *of)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use pallet::*;
|
|
@ -6,8 +6,8 @@ use sc_service::ChainType;
|
||||||
|
|
||||||
use serai_runtime::{
|
use serai_runtime::{
|
||||||
primitives::*, WASM_BINARY, opaque::SessionKeys, BABE_GENESIS_EPOCH_CONFIG, RuntimeGenesisConfig,
|
primitives::*, WASM_BINARY, opaque::SessionKeys, BABE_GENESIS_EPOCH_CONFIG, RuntimeGenesisConfig,
|
||||||
SystemConfig, ValidatorSetsConfig, SessionConfig, BabeConfig, GrandpaConfig,
|
SystemConfig, CoinsConfig, DexConfig, ValidatorSetsConfig, SessionConfig, BabeConfig,
|
||||||
AuthorityDiscoveryConfig, CoinsConfig,
|
GrandpaConfig, AuthorityDiscoveryConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type ChainSpec = sc_service::GenericChainSpec<RuntimeGenesisConfig>;
|
pub type ChainSpec = sc_service::GenericChainSpec<RuntimeGenesisConfig>;
|
||||||
|
@ -43,6 +43,8 @@ fn testnet_genesis(
|
||||||
.collect(),
|
.collect(),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
dex: DexConfig { pools: vec![Coin::Bitcoin, Coin::Ether, Coin::Dai, Coin::Monero] },
|
||||||
|
|
||||||
validator_sets: ValidatorSetsConfig {
|
validator_sets: ValidatorSetsConfig {
|
||||||
networks: serai_runtime::primitives::NETWORKS
|
networks: serai_runtime::primitives::NETWORKS
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
@ -42,6 +42,8 @@ pub const COINS: [Coin; 5] = [Coin::Serai, Coin::Bitcoin, Coin::Ether, Coin::Dai
|
||||||
Copy,
|
Copy,
|
||||||
PartialEq,
|
PartialEq,
|
||||||
Eq,
|
Eq,
|
||||||
|
PartialOrd,
|
||||||
|
Ord,
|
||||||
Hash,
|
Hash,
|
||||||
Debug,
|
Debug,
|
||||||
Serialize,
|
Serialize,
|
||||||
|
@ -61,6 +63,10 @@ pub enum Coin {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Coin {
|
impl Coin {
|
||||||
|
pub fn native() -> Coin {
|
||||||
|
Coin::Serai
|
||||||
|
}
|
||||||
|
|
||||||
pub fn network(&self) -> NetworkId {
|
pub fn network(&self) -> NetworkId {
|
||||||
match self {
|
match self {
|
||||||
Coin::Serai => NetworkId::Serai,
|
Coin::Serai => NetworkId::Serai,
|
||||||
|
@ -101,6 +107,10 @@ impl Coin {
|
||||||
Coin::Monero => 12,
|
Coin::Monero => 12,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_native(&self) -> bool {
|
||||||
|
matches!(self, Coin::Serai)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Max of 8 coins per network
|
// Max of 8 coins per network
|
||||||
|
|
|
@ -40,12 +40,15 @@ frame-executive = { git = "https://github.com/serai-dex/substrate", default-feat
|
||||||
frame-benchmarking = { git = "https://github.com/serai-dex/substrate", default-features = false, optional = true }
|
frame-benchmarking = { git = "https://github.com/serai-dex/substrate", default-features = false, optional = true }
|
||||||
|
|
||||||
serai-primitives = { path = "../primitives", default-features = false }
|
serai-primitives = { path = "../primitives", default-features = false }
|
||||||
|
serai-dex-primitives = { path = "../dex/primitives", default-features = false }
|
||||||
|
|
||||||
pallet-timestamp = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
pallet-timestamp = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
coins-pallet = { package = "serai-coins-pallet", path = "../coins/pallet", default-features = false }
|
coins-pallet = { package = "serai-coins-pallet", path = "../coins/pallet", default-features = false }
|
||||||
|
liquidity-tokens-pallet = { package = "serai-liquidity-tokens-pallet", path = "../liquidity-tokens/pallet", default-features = false }
|
||||||
|
dex-pallet = { package = "serai-dex-pallet", path = "../dex/pallet", default-features = false }
|
||||||
|
|
||||||
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets/pallet", default-features = false }
|
validator-sets-pallet = { package = "serai-validator-sets-pallet", path = "../validator-sets/pallet", default-features = false }
|
||||||
pallet-session = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
pallet-session = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
@ -94,12 +97,15 @@ std = [
|
||||||
"frame-executive/std",
|
"frame-executive/std",
|
||||||
|
|
||||||
"serai-primitives/std",
|
"serai-primitives/std",
|
||||||
|
"serai-dex-primitives/std",
|
||||||
|
|
||||||
"pallet-timestamp/std",
|
"pallet-timestamp/std",
|
||||||
|
|
||||||
"pallet-transaction-payment/std",
|
"pallet-transaction-payment/std",
|
||||||
|
|
||||||
"coins-pallet/std",
|
"coins-pallet/std",
|
||||||
|
"liquidity-tokens-pallet/std",
|
||||||
|
"dex-pallet/std",
|
||||||
|
|
||||||
"validator-sets-pallet/std",
|
"validator-sets-pallet/std",
|
||||||
"pallet-session/std",
|
"pallet-session/std",
|
||||||
|
@ -126,6 +132,8 @@ runtime-benchmarks = [
|
||||||
|
|
||||||
"pallet-timestamp/runtime-benchmarks",
|
"pallet-timestamp/runtime-benchmarks",
|
||||||
|
|
||||||
|
"dex-pallet/runtime-benchmarks",
|
||||||
|
|
||||||
"pallet-babe/runtime-benchmarks",
|
"pallet-babe/runtime-benchmarks",
|
||||||
"pallet-grandpa/runtime-benchmarks",
|
"pallet-grandpa/runtime-benchmarks",
|
||||||
]
|
]
|
||||||
|
|
|
@ -17,6 +17,8 @@ pub use pallet_timestamp as timestamp;
|
||||||
pub use pallet_transaction_payment as transaction_payment;
|
pub use pallet_transaction_payment as transaction_payment;
|
||||||
|
|
||||||
pub use coins_pallet as coins;
|
pub use coins_pallet as coins;
|
||||||
|
pub use liquidity_tokens_pallet as liquidity_tokens;
|
||||||
|
pub use dex_pallet as dex;
|
||||||
|
|
||||||
pub use validator_sets_pallet as validator_sets;
|
pub use validator_sets_pallet as validator_sets;
|
||||||
pub use pallet_session as session;
|
pub use pallet_session as session;
|
||||||
|
@ -45,7 +47,7 @@ use sp_runtime::{
|
||||||
ApplyExtrinsicResult, Perbill,
|
ApplyExtrinsicResult, Perbill,
|
||||||
};
|
};
|
||||||
|
|
||||||
use primitives::{PublicKey, SeraiAddress, AccountLookup, Signature, SubstrateAmount};
|
use primitives::{PublicKey, SeraiAddress, Coin, AccountLookup, Signature, SubstrateAmount};
|
||||||
|
|
||||||
use support::{
|
use support::{
|
||||||
traits::{ConstU8, ConstU32, ConstU64, Contains},
|
traits::{ConstU8, ConstU32, ConstU64, Contains},
|
||||||
|
@ -167,6 +169,7 @@ impl Contains<RuntimeCall> for CallFilter {
|
||||||
|
|
||||||
// All of these pallets are our own, and all of their written calls are intended to be called
|
// All of these pallets are our own, and all of their written calls are intended to be called
|
||||||
RuntimeCall::Coins(call) => !matches!(call, coins::Call::__Ignore(_, _)),
|
RuntimeCall::Coins(call) => !matches!(call, coins::Call::__Ignore(_, _)),
|
||||||
|
RuntimeCall::Dex(call) => !matches!(call, dex::Call::__Ignore(_, _)),
|
||||||
RuntimeCall::ValidatorSets(call) => !matches!(call, validator_sets::Call::__Ignore(_, _)),
|
RuntimeCall::ValidatorSets(call) => !matches!(call, validator_sets::Call::__Ignore(_, _)),
|
||||||
RuntimeCall::InInstructions(call) => !matches!(call, in_instructions::Call::__Ignore(_, _)),
|
RuntimeCall::InInstructions(call) => !matches!(call, in_instructions::Call::__Ignore(_, _)),
|
||||||
RuntimeCall::Signals(call) => !matches!(call, signals::Call::__Ignore(_, _)),
|
RuntimeCall::Signals(call) => !matches!(call, signals::Call::__Ignore(_, _)),
|
||||||
|
@ -243,6 +246,37 @@ impl coins::Config for Runtime {
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl liquidity_tokens::Config for Runtime {
|
||||||
|
type RuntimeEvent = RuntimeEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl dex::Config for Runtime {
|
||||||
|
type RuntimeEvent = RuntimeEvent;
|
||||||
|
type Currency = Coins;
|
||||||
|
type Balance = SubstrateAmount;
|
||||||
|
type CoinBalance = SubstrateAmount;
|
||||||
|
// TODO: Review if this should be u64/u128 or u64/u256 (and rounding in general).
|
||||||
|
type HigherPrecisionBalance = u128;
|
||||||
|
|
||||||
|
type CoinId = Coin;
|
||||||
|
type MultiCoinId = Coin;
|
||||||
|
type MultiCoinIdConverter = serai_dex_primitives::CoinConverter;
|
||||||
|
type PoolCoinId = u32;
|
||||||
|
|
||||||
|
type Coins = Coins;
|
||||||
|
type PoolCoins = LiquidityTokens;
|
||||||
|
|
||||||
|
type LPFee = ConstU32<3>; // 0.3%
|
||||||
|
type MintMinLiquidity = ConstU64<10000>;
|
||||||
|
|
||||||
|
type MaxSwapPathLength = ConstU32<3>; // coin1 -> SRI -> coin2
|
||||||
|
|
||||||
|
type WeightInfo = dex::weights::SubstrateWeight<Runtime>;
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime-benchmarks")]
|
||||||
|
type BenchmarkHelper = ();
|
||||||
|
}
|
||||||
|
|
||||||
impl validator_sets::Config for Runtime {
|
impl validator_sets::Config for Runtime {
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
}
|
}
|
||||||
|
@ -342,6 +376,8 @@ construct_runtime!(
|
||||||
TransactionPayment: transaction_payment,
|
TransactionPayment: transaction_payment,
|
||||||
|
|
||||||
Coins: coins,
|
Coins: coins,
|
||||||
|
LiquidityTokens: liquidity_tokens,
|
||||||
|
Dex: dex,
|
||||||
|
|
||||||
ValidatorSets: validator_sets,
|
ValidatorSets: validator_sets,
|
||||||
Session: session,
|
Session: session,
|
||||||
|
|
|
@ -409,7 +409,7 @@ pub mod pallet {
|
||||||
|
|
||||||
impl<T: Config> Pallet<T> {
|
impl<T: Config> Pallet<T> {
|
||||||
fn account() -> T::AccountId {
|
fn account() -> T::AccountId {
|
||||||
system_address(b"validator-sets").into()
|
system_address(b"ValidatorSets").into()
|
||||||
}
|
}
|
||||||
|
|
||||||
// is_bft returns if the network is able to survive any single node becoming byzantine.
|
// is_bft returns if the network is able to survive any single node becoming byzantine.
|
||||||
|
|
Loading…
Reference in a new issue