mirror of
https://github.com/serai-dex/serai.git
synced 2025-02-02 19:26:26 +00:00
Coins pallet (#399)
* initial implementation * add function to get a balance of an account * add support for multiple coins * rename pallet to "coins-pallet" * replace balances, assets and tokens pallet with coins pallet in runtime * add total supply info * update client side for new Coins pallet * handle fees * bug fixes * Update FeeAccount test * Fmt * fix pr comments * remove extraneous Imbalance type * Minor tweaks --------- Co-authored-by: Luke Parker <lukeparker5132@gmail.com>
This commit is contained in:
parent
3255c0ace5
commit
fdfce9e207
32 changed files with 535 additions and 445 deletions
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
|
@ -60,8 +60,8 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features \
|
GITHUB_CI=true RUST_BACKTRACE=1 cargo test --all-features \
|
||||||
-p serai-primitives \
|
-p serai-primitives \
|
||||||
-p serai-tokens-primitives \
|
-p serai-coins-primitives \
|
||||||
-p serai-tokens-pallet \
|
-p serai-coins-pallet \
|
||||||
-p serai-in-instructions-primitives \
|
-p serai-in-instructions-primitives \
|
||||||
-p serai-in-instructions-pallet \
|
-p serai-in-instructions-pallet \
|
||||||
-p serai-validator-sets-primitives \
|
-p serai-validator-sets-primitives \
|
||||||
|
|
95
Cargo.lock
generated
95
Cargo.lock
generated
|
@ -5488,21 +5488,6 @@ dependencies = [
|
||||||
"group",
|
"group",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pallet-assets"
|
|
||||||
version = "4.0.0-dev"
|
|
||||||
source = "git+https://github.com/serai-dex/substrate#98ab693fdf71f371d5059aa6924a410c8bb0a675"
|
|
||||||
dependencies = [
|
|
||||||
"frame-benchmarking",
|
|
||||||
"frame-support",
|
|
||||||
"frame-system",
|
|
||||||
"parity-scale-codec",
|
|
||||||
"scale-info",
|
|
||||||
"sp-core",
|
|
||||||
"sp-runtime",
|
|
||||||
"sp-std",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pallet-authority-discovery"
|
name = "pallet-authority-discovery"
|
||||||
version = "4.0.0-dev"
|
version = "4.0.0-dev"
|
||||||
|
@ -5557,21 +5542,6 @@ dependencies = [
|
||||||
"sp-std",
|
"sp-std",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pallet-balances"
|
|
||||||
version = "4.0.0-dev"
|
|
||||||
source = "git+https://github.com/serai-dex/substrate#98ab693fdf71f371d5059aa6924a410c8bb0a675"
|
|
||||||
dependencies = [
|
|
||||||
"frame-benchmarking",
|
|
||||||
"frame-support",
|
|
||||||
"frame-system",
|
|
||||||
"log",
|
|
||||||
"parity-scale-codec",
|
|
||||||
"scale-info",
|
|
||||||
"sp-runtime",
|
|
||||||
"sp-std",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pallet-grandpa"
|
name = "pallet-grandpa"
|
||||||
version = "4.0.0-dev"
|
version = "4.0.0-dev"
|
||||||
|
@ -8221,6 +8191,34 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serai-coins-pallet"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"frame-support",
|
||||||
|
"frame-system",
|
||||||
|
"pallet-transaction-payment",
|
||||||
|
"parity-scale-codec",
|
||||||
|
"scale-info",
|
||||||
|
"serai-coins-primitives",
|
||||||
|
"serai-primitives",
|
||||||
|
"sp-core",
|
||||||
|
"sp-runtime",
|
||||||
|
"sp-std",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serai-coins-primitives"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"parity-scale-codec",
|
||||||
|
"scale-info",
|
||||||
|
"serai-primitives",
|
||||||
|
"serde",
|
||||||
|
"sp-runtime",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serai-coordinator"
|
name = "serai-coordinator"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -8325,9 +8323,9 @@ dependencies = [
|
||||||
"frame-system",
|
"frame-system",
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
"scale-info",
|
"scale-info",
|
||||||
|
"serai-coins-pallet",
|
||||||
"serai-in-instructions-primitives",
|
"serai-in-instructions-primitives",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serai-tokens-pallet",
|
|
||||||
"serai-validator-sets-pallet",
|
"serai-validator-sets-pallet",
|
||||||
"sp-application-crypto",
|
"sp-application-crypto",
|
||||||
"sp-core",
|
"sp-core",
|
||||||
|
@ -8342,8 +8340,8 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
"scale-info",
|
"scale-info",
|
||||||
|
"serai-coins-primitives",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serai-tokens-primitives",
|
|
||||||
"serde",
|
"serde",
|
||||||
"sp-application-crypto",
|
"sp-application-crypto",
|
||||||
"sp-runtime",
|
"sp-runtime",
|
||||||
|
@ -8503,9 +8501,9 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dkg",
|
"dkg",
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
|
"serai-coins-primitives",
|
||||||
"serai-in-instructions-primitives",
|
"serai-in-instructions-primitives",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serai-tokens-primitives",
|
|
||||||
"serai-validator-sets-primitives",
|
"serai-validator-sets-primitives",
|
||||||
"serde",
|
"serde",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
|
@ -8556,10 +8554,8 @@ dependencies = [
|
||||||
"frame-support",
|
"frame-support",
|
||||||
"frame-system",
|
"frame-system",
|
||||||
"frame-system-rpc-runtime-api",
|
"frame-system-rpc-runtime-api",
|
||||||
"pallet-assets",
|
|
||||||
"pallet-authority-discovery",
|
"pallet-authority-discovery",
|
||||||
"pallet-babe",
|
"pallet-babe",
|
||||||
"pallet-balances",
|
|
||||||
"pallet-grandpa",
|
"pallet-grandpa",
|
||||||
"pallet-session",
|
"pallet-session",
|
||||||
"pallet-timestamp",
|
"pallet-timestamp",
|
||||||
|
@ -8567,10 +8563,10 @@ dependencies = [
|
||||||
"pallet-transaction-payment-rpc-runtime-api",
|
"pallet-transaction-payment-rpc-runtime-api",
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
"scale-info",
|
"scale-info",
|
||||||
|
"serai-coins-pallet",
|
||||||
"serai-in-instructions-pallet",
|
"serai-in-instructions-pallet",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serai-staking-pallet",
|
"serai-staking-pallet",
|
||||||
"serai-tokens-pallet",
|
|
||||||
"serai-validator-sets-pallet",
|
"serai-validator-sets-pallet",
|
||||||
"sp-api",
|
"sp-api",
|
||||||
"sp-authority-discovery",
|
"sp-authority-discovery",
|
||||||
|
@ -8597,38 +8593,13 @@ dependencies = [
|
||||||
"pallet-session",
|
"pallet-session",
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
"scale-info",
|
"scale-info",
|
||||||
|
"serai-coins-pallet",
|
||||||
"serai-primitives",
|
"serai-primitives",
|
||||||
"serai-validator-sets-pallet",
|
"serai-validator-sets-pallet",
|
||||||
"serai-validator-sets-primitives",
|
|
||||||
"sp-runtime",
|
"sp-runtime",
|
||||||
"sp-std",
|
"sp-std",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serai-tokens-pallet"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"frame-support",
|
|
||||||
"frame-system",
|
|
||||||
"pallet-assets",
|
|
||||||
"parity-scale-codec",
|
|
||||||
"scale-info",
|
|
||||||
"serai-primitives",
|
|
||||||
"serai-tokens-primitives",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "serai-tokens-primitives"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"parity-scale-codec",
|
|
||||||
"scale-info",
|
|
||||||
"serai-primitives",
|
|
||||||
"serde",
|
|
||||||
"sp-runtime",
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serai-validator-sets-pallet"
|
name = "serai-validator-sets-pallet"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -37,8 +37,8 @@ members = [
|
||||||
|
|
||||||
"substrate/primitives",
|
"substrate/primitives",
|
||||||
|
|
||||||
"substrate/tokens/primitives",
|
"substrate/coins/primitives",
|
||||||
"substrate/tokens/pallet",
|
"substrate/coins/pallet",
|
||||||
|
|
||||||
"substrate/in-instructions/primitives",
|
"substrate/in-instructions/primitives",
|
||||||
"substrate/in-instructions/pallet",
|
"substrate/in-instructions/pallet",
|
||||||
|
|
|
@ -16,7 +16,7 @@ use serai_client::{
|
||||||
ValidatorSetsEvent,
|
ValidatorSetsEvent,
|
||||||
},
|
},
|
||||||
in_instructions::InInstructionsEvent,
|
in_instructions::InInstructionsEvent,
|
||||||
coins::{primitives::OutInstructionWithBalance, TokensEvent},
|
coins::CoinsEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serai_db::DbTxn;
|
use serai_db::DbTxn;
|
||||||
|
@ -209,12 +209,12 @@ async fn handle_batch_and_burns<D: Db, Pro: Processors>(
|
||||||
}
|
}
|
||||||
|
|
||||||
for burn in serai.coins().burn_events().await? {
|
for burn in serai.coins().burn_events().await? {
|
||||||
if let TokensEvent::Burn { address: _, balance, instruction } = burn {
|
if let CoinsEvent::Burn { address: _, instruction } = burn {
|
||||||
let network = balance.coin.network();
|
let network = instruction.balance.coin.network();
|
||||||
network_had_event(&mut burns, &mut batches, network);
|
network_had_event(&mut burns, &mut batches, network);
|
||||||
|
|
||||||
// network_had_event should register an entry in burns
|
// network_had_event should register an entry in burns
|
||||||
burns.get_mut(&network).unwrap().push(OutInstructionWithBalance { balance, instruction });
|
burns.get_mut(&network).unwrap().push(instruction);
|
||||||
} else {
|
} else {
|
||||||
panic!("Burn event wasn't Burn: {burn:?}");
|
panic!("Burn event wasn't Burn: {burn:?}");
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ exceptions = [
|
||||||
{ allow = ["AGPL-3.0"], name = "tributary-chain" },
|
{ allow = ["AGPL-3.0"], name = "tributary-chain" },
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-coordinator" },
|
{ allow = ["AGPL-3.0"], name = "serai-coordinator" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-tokens-pallet" },
|
{ allow = ["AGPL-3.0"], name = "serai-coins-pallet" },
|
||||||
|
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-in-instructions-pallet" },
|
{ allow = ["AGPL-3.0"], name = "serai-in-instructions-pallet" },
|
||||||
|
|
||||||
|
|
|
@ -23,5 +23,5 @@ dkg = { path = "../../crypto/dkg", features = ["serde"] }
|
||||||
|
|
||||||
serai-primitives = { path = "../../substrate/primitives" }
|
serai-primitives = { path = "../../substrate/primitives" }
|
||||||
in-instructions-primitives = { package = "serai-in-instructions-primitives", path = "../../substrate/in-instructions/primitives" }
|
in-instructions-primitives = { package = "serai-in-instructions-primitives", path = "../../substrate/in-instructions/primitives" }
|
||||||
tokens-primitives = { package = "serai-tokens-primitives", path = "../../substrate/tokens/primitives" }
|
coins-primitives = { package = "serai-coins-primitives", path = "../../substrate/coins/primitives" }
|
||||||
validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../../substrate/validator-sets/primitives" }
|
validator-sets-primitives = { package = "serai-validator-sets-primitives", path = "../../substrate/validator-sets/primitives" }
|
||||||
|
|
|
@ -9,7 +9,7 @@ use dkg::{Participant, ThresholdParams};
|
||||||
|
|
||||||
use serai_primitives::{BlockHash, NetworkId};
|
use serai_primitives::{BlockHash, NetworkId};
|
||||||
use in_instructions_primitives::{Batch, SignedBatch};
|
use in_instructions_primitives::{Batch, SignedBatch};
|
||||||
use tokens_primitives::OutInstructionWithBalance;
|
use coins_primitives::OutInstructionWithBalance;
|
||||||
use validator_sets_primitives::{ValidatorSet, KeyPair};
|
use validator_sets_primitives::{ValidatorSet, KeyPair};
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, Encode, Decode, Serialize, Deserialize)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize, Encode, Decode, Serialize, Deserialize)]
|
||||||
|
|
|
@ -15,7 +15,7 @@ mod other_primitives {
|
||||||
pub use serai_runtime::in_instructions::primitives;
|
pub use serai_runtime::in_instructions::primitives;
|
||||||
}
|
}
|
||||||
pub mod coins {
|
pub mod coins {
|
||||||
pub use serai_runtime::tokens::primitives;
|
pub use serai_runtime::coins::primitives;
|
||||||
}
|
}
|
||||||
pub mod validator_sets {
|
pub mod validator_sets {
|
||||||
pub use serai_runtime::validator_sets::primitives;
|
pub use serai_runtime::validator_sets::primitives;
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
use sp_core::sr25519::Public;
|
|
||||||
use serai_runtime::{
|
use serai_runtime::{
|
||||||
primitives::{SeraiAddress, SubstrateAmount, Amount, Coin, Balance},
|
primitives::{SeraiAddress, SubstrateAmount, Amount, Coin, Balance},
|
||||||
assets::{AssetDetails, AssetAccount},
|
coins, Coins, Runtime,
|
||||||
tokens, Tokens, Runtime,
|
|
||||||
};
|
};
|
||||||
pub use tokens::primitives;
|
pub use coins::primitives;
|
||||||
use primitives::OutInstruction;
|
use primitives::OutInstructionWithBalance;
|
||||||
|
|
||||||
use subxt::tx::Payload;
|
use subxt::tx::Payload;
|
||||||
|
|
||||||
use crate::{TemporalSerai, SeraiError, Composite, scale_value, scale_composite};
|
use crate::{TemporalSerai, SeraiError, Composite, scale_value, scale_composite};
|
||||||
|
|
||||||
const PALLET: &str = "Tokens";
|
const PALLET: &str = "Coins";
|
||||||
|
|
||||||
pub type TokensEvent = tokens::Event<Runtime>;
|
pub type CoinsEvent = coins::Event<Runtime>;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct SeraiCoins<'a>(pub(crate) TemporalSerai<'a>);
|
pub struct SeraiCoins<'a>(pub(crate) TemporalSerai<'a>);
|
||||||
|
@ -22,37 +20,25 @@ impl<'a> SeraiCoins<'a> {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn mint_events(&self) -> Result<Vec<TokensEvent>, SeraiError> {
|
pub async fn mint_events(&self) -> Result<Vec<CoinsEvent>, SeraiError> {
|
||||||
self.0.events::<Tokens, _>(|event| matches!(event, TokensEvent::Mint { .. })).await
|
self.0.events::<Coins, _>(|event| matches!(event, CoinsEvent::Mint { .. })).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn burn_events(&self) -> Result<Vec<TokensEvent>, SeraiError> {
|
pub async fn burn_events(&self) -> Result<Vec<CoinsEvent>, SeraiError> {
|
||||||
self.0.events::<Tokens, _>(|event| matches!(event, TokensEvent::Burn { .. })).await
|
self.0.events::<Coins, _>(|event| matches!(event, CoinsEvent::Burn { .. })).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn sri_balance(&self, address: SeraiAddress) -> Result<u64, SeraiError> {
|
pub async fn coin_supply(&self, coin: Coin) -> Result<Amount, SeraiError> {
|
||||||
let data: Option<
|
|
||||||
serai_runtime::system::AccountInfo<u32, serai_runtime::balances::AccountData<u64>>,
|
|
||||||
> = self.0.storage("System", "Account", Some(vec![scale_value(address)])).await?;
|
|
||||||
Ok(data.map(|data| data.data.free).unwrap_or(0))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn token_supply(&self, coin: Coin) -> Result<Amount, SeraiError> {
|
|
||||||
Ok(Amount(
|
Ok(Amount(
|
||||||
self
|
self
|
||||||
.0
|
.0
|
||||||
.storage::<AssetDetails<SubstrateAmount, SeraiAddress, SubstrateAmount>>(
|
.storage::<SubstrateAmount>(PALLET, "Supply", Some(vec![scale_value(coin)]))
|
||||||
"Assets",
|
|
||||||
"Asset",
|
|
||||||
Some(vec![scale_value(coin)]),
|
|
||||||
)
|
|
||||||
.await?
|
.await?
|
||||||
.map(|token| token.supply)
|
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn token_balance(
|
pub async fn coin_balance(
|
||||||
&self,
|
&self,
|
||||||
coin: Coin,
|
coin: Coin,
|
||||||
address: SeraiAddress,
|
address: SeraiAddress,
|
||||||
|
@ -60,35 +46,25 @@ impl<'a> SeraiCoins<'a> {
|
||||||
Ok(Amount(
|
Ok(Amount(
|
||||||
self
|
self
|
||||||
.0
|
.0
|
||||||
.storage::<AssetAccount<SubstrateAmount, SubstrateAmount, (), Public>>(
|
.storage::<SubstrateAmount>(
|
||||||
"Assets",
|
PALLET,
|
||||||
"Account",
|
"Balances",
|
||||||
Some(vec![scale_value(coin), scale_value(address)]),
|
Some(vec![scale_value(address), scale_value(coin)]),
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.map(|account| account.balance())
|
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transfer_sri(to: SeraiAddress, amount: Amount) -> Payload<Composite<()>> {
|
pub fn transfer(to: SeraiAddress, balance: Balance) -> Payload<Composite<()>> {
|
||||||
Payload::new(
|
Payload::new(
|
||||||
"Balances",
|
PALLET,
|
||||||
// TODO: Use transfer_allow_death?
|
|
||||||
// TODO: Replace the Balances pallet with something much simpler
|
|
||||||
"transfer",
|
"transfer",
|
||||||
scale_composite(serai_runtime::balances::Call::<Runtime>::transfer {
|
scale_composite(serai_runtime::coins::Call::<Runtime>::transfer { to, balance }),
|
||||||
dest: to,
|
|
||||||
value: amount.0,
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn burn(balance: Balance, instruction: OutInstruction) -> Payload<Composite<()>> {
|
pub fn burn(instruction: OutInstructionWithBalance) -> Payload<Composite<()>> {
|
||||||
Payload::new(
|
Payload::new(PALLET, "burn", scale_composite(coins::Call::<Runtime>::burn { instruction }))
|
||||||
PALLET,
|
|
||||||
"burn",
|
|
||||||
scale_composite(tokens::Call::<Runtime>::burn { balance, instruction }),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use serai_client::{
|
||||||
primitives::{InInstruction, InInstructionWithBalance, Batch},
|
primitives::{InInstruction, InInstructionWithBalance, Batch},
|
||||||
InInstructionsEvent,
|
InInstructionsEvent,
|
||||||
},
|
},
|
||||||
coins::TokensEvent,
|
coins::CoinsEvent,
|
||||||
Serai,
|
Serai,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -65,8 +65,8 @@ serai_test!(
|
||||||
}
|
}
|
||||||
|
|
||||||
let serai = serai.coins();
|
let serai = serai.coins();
|
||||||
assert_eq!(serai.mint_events().await.unwrap(), vec![TokensEvent::Mint { address, balance }],);
|
assert_eq!(serai.mint_events().await.unwrap(), vec![CoinsEvent::Mint { address, balance }],);
|
||||||
assert_eq!(serai.token_supply(coin).await.unwrap(), amount);
|
assert_eq!(serai.coin_supply(coin).await.unwrap(), amount);
|
||||||
assert_eq!(serai.token_balance(coin, address).await.unwrap(), amount);
|
assert_eq!(serai.coin_balance(coin, address).await.unwrap(), amount);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,6 +7,7 @@ use blake2::{
|
||||||
|
|
||||||
use scale::Encode;
|
use scale::Encode;
|
||||||
|
|
||||||
|
use serai_runtime::coins::primitives::OutInstructionWithBalance;
|
||||||
use sp_core::Pair;
|
use sp_core::Pair;
|
||||||
|
|
||||||
use serai_client::{
|
use serai_client::{
|
||||||
|
@ -19,7 +20,7 @@ use serai_client::{
|
||||||
InInstructionsEvent,
|
InInstructionsEvent,
|
||||||
primitives::{InInstruction, InInstructionWithBalance, Batch},
|
primitives::{InInstruction, InInstructionWithBalance, Batch},
|
||||||
},
|
},
|
||||||
coins::{primitives::OutInstruction, TokensEvent},
|
coins::{primitives::OutInstruction, CoinsEvent},
|
||||||
PairSigner, Serai, SeraiCoins,
|
PairSigner, Serai, SeraiCoins,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,10 +70,10 @@ serai_test!(
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serai.coins().mint_events().await.unwrap(),
|
serai.coins().mint_events().await.unwrap(),
|
||||||
vec![TokensEvent::Mint { address, balance }]
|
vec![CoinsEvent::Mint { address, balance }]
|
||||||
);
|
);
|
||||||
assert_eq!(serai.coins().token_supply(coin).await.unwrap(), amount);
|
assert_eq!(serai.coins().coin_supply(coin).await.unwrap(), amount);
|
||||||
assert_eq!(serai.coins().token_balance(coin, address).await.unwrap(), amount);
|
assert_eq!(serai.coins().coin_balance(coin, address).await.unwrap(), amount);
|
||||||
|
|
||||||
// Now burn it
|
// Now burn it
|
||||||
let mut rand_bytes = vec![0; 32];
|
let mut rand_bytes = vec![0; 32];
|
||||||
|
@ -83,13 +84,16 @@ serai_test!(
|
||||||
OsRng.fill_bytes(&mut rand_bytes);
|
OsRng.fill_bytes(&mut rand_bytes);
|
||||||
let data = Data::new(rand_bytes).unwrap();
|
let data = Data::new(rand_bytes).unwrap();
|
||||||
|
|
||||||
let out = OutInstruction { address: external_address, data: Some(data) };
|
let instruction = OutInstructionWithBalance {
|
||||||
|
balance,
|
||||||
|
instruction: OutInstruction { address: external_address, data: Some(data) },
|
||||||
|
};
|
||||||
let serai = serai.into_inner();
|
let serai = serai.into_inner();
|
||||||
let block = publish_tx(
|
let block = publish_tx(
|
||||||
&serai
|
&serai
|
||||||
.sign(
|
.sign(
|
||||||
&PairSigner::new(pair),
|
&PairSigner::new(pair),
|
||||||
&SeraiCoins::burn(balance, out.clone()),
|
&SeraiCoins::burn(instruction.clone()),
|
||||||
0,
|
0,
|
||||||
BaseExtrinsicParamsBuilder::new(),
|
BaseExtrinsicParamsBuilder::new(),
|
||||||
)
|
)
|
||||||
|
@ -99,8 +103,8 @@ serai_test!(
|
||||||
|
|
||||||
let serai = serai.as_of(block).coins();
|
let serai = serai.as_of(block).coins();
|
||||||
let events = serai.burn_events().await.unwrap();
|
let events = serai.burn_events().await.unwrap();
|
||||||
assert_eq!(events, vec![TokensEvent::Burn { address, balance, instruction: out }]);
|
assert_eq!(events, vec![CoinsEvent::Burn { address, instruction }]);
|
||||||
assert_eq!(serai.token_supply(coin).await.unwrap(), Amount(0));
|
assert_eq!(serai.coin_supply(coin).await.unwrap(), Amount(0));
|
||||||
assert_eq!(serai.token_balance(coin, address).await.unwrap(), Amount(0));
|
assert_eq!(serai.coin_balance(coin, address).await.unwrap(), Amount(0));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
49
substrate/coins/pallet/Cargo.toml
Normal file
49
substrate/coins/pallet/Cargo.toml
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
[package]
|
||||||
|
name = "serai-coins-pallet"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Coins pallet for Serai"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/substrate/coins/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-std = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
|
pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
|
serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
|
coins-primitives = { package = "serai-coins-primitives", path = "../primitives", default-features = false }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
std = [
|
||||||
|
"frame-system/std",
|
||||||
|
"frame-support/std",
|
||||||
|
|
||||||
|
"sp-std/std",
|
||||||
|
"sp-runtime/std",
|
||||||
|
|
||||||
|
"pallet-transaction-payment/std",
|
||||||
|
|
||||||
|
"serai-primitives/std",
|
||||||
|
"coins-primitives/std",
|
||||||
|
]
|
||||||
|
|
||||||
|
runtime-benchmarks = [
|
||||||
|
"frame-system/runtime-benchmarks",
|
||||||
|
"frame-support/runtime-benchmarks",
|
||||||
|
]
|
||||||
|
|
||||||
|
default = ["std"]
|
278
substrate/coins/pallet/src/lib.rs
Normal file
278
substrate/coins/pallet/src/lib.rs
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
|
#[frame_support::pallet]
|
||||||
|
pub mod pallet {
|
||||||
|
use sp_std::vec::Vec;
|
||||||
|
use sp_core::sr25519::Public;
|
||||||
|
use sp_runtime::{
|
||||||
|
traits::{DispatchInfoOf, PostDispatchInfoOf},
|
||||||
|
transaction_validity::{TransactionValidityError, InvalidTransaction},
|
||||||
|
};
|
||||||
|
|
||||||
|
use frame_system::pallet_prelude::*;
|
||||||
|
use frame_support::pallet_prelude::*;
|
||||||
|
|
||||||
|
use pallet_transaction_payment::{Config as TpConfig, OnChargeTransaction};
|
||||||
|
|
||||||
|
use serai_primitives::*;
|
||||||
|
pub use coins_primitives as primitives;
|
||||||
|
use primitives::*;
|
||||||
|
|
||||||
|
#[pallet::config]
|
||||||
|
pub trait Config: frame_system::Config<AccountId = Public> + TpConfig {
|
||||||
|
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pallet::genesis_config]
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode)]
|
||||||
|
pub struct GenesisConfig<T: Config> {
|
||||||
|
_config: PhantomData<T>,
|
||||||
|
pub accounts: Vec<(Public, Balance)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Config> Default for GenesisConfig<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
GenesisConfig { _config: PhantomData, accounts: Default::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pallet::error]
|
||||||
|
pub enum Error<T> {
|
||||||
|
AmountOverflowed,
|
||||||
|
NotEnoughCoins,
|
||||||
|
SriBurnNotAllowed,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pallet::event]
|
||||||
|
#[pallet::generate_deposit(fn deposit_event)]
|
||||||
|
pub enum Event<T: Config> {
|
||||||
|
Mint { to: Public, balance: Balance },
|
||||||
|
Burn { from: Public, instruction: OutInstructionWithBalance },
|
||||||
|
SriBurn { from: Public, amount: Amount },
|
||||||
|
Transfer { from: Public, to: Public, balance: Balance },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
Identity,
|
||||||
|
Coin,
|
||||||
|
SubstrateAmount,
|
||||||
|
ValueQuery,
|
||||||
|
>;
|
||||||
|
|
||||||
|
/// 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<_, Identity, Coin, SubstrateAmount, ValueQuery>;
|
||||||
|
|
||||||
|
#[pallet::genesis_build]
|
||||||
|
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||||
|
fn build(&self) {
|
||||||
|
// initialize the supply of the coins
|
||||||
|
for c in COINS.iter() {
|
||||||
|
Supply::<T>::set(c, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize the genesis accounts
|
||||||
|
for (account, balance) in self.accounts.iter() {
|
||||||
|
Pallet::<T>::mint(*account, *balance).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pallet::hooks]
|
||||||
|
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||||
|
fn on_initialize(_: BlockNumberFor<T>) -> Weight {
|
||||||
|
// burn the fees collected previous block
|
||||||
|
let coin = Coin::Serai;
|
||||||
|
let amount = Self::balance(FEE_ACCOUNT.into(), coin);
|
||||||
|
// we can unwrap, we are not burning more then what we have
|
||||||
|
// If this errors, it'll halt the runtime however (due to being called at the start of every
|
||||||
|
// block), requiring extra care when reviewing
|
||||||
|
Self::burn_sri(FEE_ACCOUNT.into(), amount).unwrap();
|
||||||
|
Weight::zero() // TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Config> Pallet<T> {
|
||||||
|
/// Returns the balance of a given account for `coin`.
|
||||||
|
pub fn balance(of: Public, coin: Coin) -> Amount {
|
||||||
|
Amount(Self::balances(of, coin))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decrease_balance_internal(from: Public, balance: Balance) -> Result<(), Error<T>> {
|
||||||
|
let coin = &balance.coin;
|
||||||
|
|
||||||
|
// sub amount from account
|
||||||
|
let new_amount = Self::balances(from, coin)
|
||||||
|
.checked_sub(balance.amount.0)
|
||||||
|
.ok_or(Error::<T>::NotEnoughCoins)?;
|
||||||
|
|
||||||
|
// save
|
||||||
|
if new_amount == 0 {
|
||||||
|
Balances::<T>::remove(from, coin);
|
||||||
|
} else {
|
||||||
|
Balances::<T>::set(from, coin, new_amount);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn increase_balance_internal(to: Public, balance: Balance) -> Result<(), Error<T>> {
|
||||||
|
let coin = &balance.coin;
|
||||||
|
|
||||||
|
// sub amount from account
|
||||||
|
let new_amount = Self::balances(to, coin)
|
||||||
|
.checked_add(balance.amount.0)
|
||||||
|
.ok_or(Error::<T>::AmountOverflowed)?;
|
||||||
|
|
||||||
|
// save
|
||||||
|
Balances::<T>::set(to, coin, new_amount);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mint `balance` to the given account.
|
||||||
|
///
|
||||||
|
/// Errors if any amount overflows.
|
||||||
|
pub fn mint(to: Public, balance: Balance) -> Result<(), Error<T>> {
|
||||||
|
// update the balance
|
||||||
|
Self::increase_balance_internal(to, balance)?;
|
||||||
|
|
||||||
|
// update the supply
|
||||||
|
let new_supply = Self::supply(balance.coin)
|
||||||
|
.checked_add(balance.amount.0)
|
||||||
|
.ok_or(Error::<T>::AmountOverflowed)?;
|
||||||
|
Supply::<T>::set(balance.coin, new_supply);
|
||||||
|
|
||||||
|
Self::deposit_event(Event::Mint { to, balance });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Burn `balance` from the specified account.
|
||||||
|
fn burn_internal(
|
||||||
|
from: Public,
|
||||||
|
balance: Balance,
|
||||||
|
) -> Result<(), Error<T>> {
|
||||||
|
// don't waste time if amount == 0
|
||||||
|
if balance.amount.0 == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the balance
|
||||||
|
Self::decrease_balance_internal(from, balance)?;
|
||||||
|
|
||||||
|
// update the supply
|
||||||
|
let new_supply = Self::supply(balance.coin)
|
||||||
|
.checked_sub(balance.amount.0)
|
||||||
|
.unwrap();
|
||||||
|
Supply::<T>::set(balance.coin, new_supply);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn burn_sri(
|
||||||
|
from: Public,
|
||||||
|
amount: Amount,
|
||||||
|
) -> Result<(), Error<T>> {
|
||||||
|
Self::burn_internal(from, Balance { coin: Coin::Serai, amount })?;
|
||||||
|
Self::deposit_event(Event::SriBurn { from, amount });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn burn_non_sri(
|
||||||
|
from: Public,
|
||||||
|
instruction: OutInstructionWithBalance,
|
||||||
|
) -> Result<(), Error<T>> {
|
||||||
|
if instruction.balance.coin == Coin::Serai {
|
||||||
|
Err(Error::<T>::SriBurnNotAllowed)?;
|
||||||
|
}
|
||||||
|
Self::burn_internal(from, instruction.balance)?;
|
||||||
|
Self::deposit_event(Event::Burn { from, instruction });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transfer `balance` from `from` to `to`.
|
||||||
|
pub fn transfer_internal(
|
||||||
|
from: Public,
|
||||||
|
to: Public,
|
||||||
|
balance: Balance,
|
||||||
|
) -> Result<(), Error<T>> {
|
||||||
|
// update balances of accounts
|
||||||
|
Self::decrease_balance_internal(from, balance)?;
|
||||||
|
Self::increase_balance_internal(to, balance)?;
|
||||||
|
Self::deposit_event(Event::Transfer { from, to, balance });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pallet::call]
|
||||||
|
impl<T: Config> Pallet<T> {
|
||||||
|
#[pallet::call_index(0)]
|
||||||
|
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
||||||
|
pub fn transfer(origin: OriginFor<T>, to: Public, balance: Balance) -> DispatchResult {
|
||||||
|
let from = ensure_signed(origin)?;
|
||||||
|
Self::transfer_internal(from, to, balance)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pallet::call_index(1)]
|
||||||
|
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
||||||
|
pub fn burn(origin: OriginFor<T>, instruction: OutInstructionWithBalance) -> DispatchResult {
|
||||||
|
let from = ensure_signed(origin)?;
|
||||||
|
Self::burn_non_sri(from, instruction)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Config> OnChargeTransaction<T> for Pallet<T> {
|
||||||
|
type Balance = SubstrateAmount;
|
||||||
|
type LiquidityInfo = Option<SubstrateAmount>;
|
||||||
|
|
||||||
|
fn withdraw_fee(
|
||||||
|
who: &Public,
|
||||||
|
_call: &T::RuntimeCall,
|
||||||
|
_dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
|
||||||
|
fee: Self::Balance,
|
||||||
|
_tip: Self::Balance,
|
||||||
|
) -> Result<Self::LiquidityInfo, TransactionValidityError> {
|
||||||
|
if fee == 0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let balance = Balance { coin: Coin::Serai, amount: Amount(fee) };
|
||||||
|
match Self::transfer_internal(*who, FEE_ACCOUNT.into(), balance) {
|
||||||
|
Err(_) => Err(InvalidTransaction::Payment)?,
|
||||||
|
Ok(()) => Ok(Some(fee)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn correct_and_deposit_fee(
|
||||||
|
who: &Public,
|
||||||
|
_dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
|
||||||
|
_post_info: &PostDispatchInfoOf<T::RuntimeCall>,
|
||||||
|
corrected_fee: Self::Balance,
|
||||||
|
_tip: Self::Balance,
|
||||||
|
already_withdrawn: Self::LiquidityInfo,
|
||||||
|
) -> Result<(), TransactionValidityError> {
|
||||||
|
if let Some(paid) = already_withdrawn {
|
||||||
|
let refund_amount = paid.saturating_sub(corrected_fee);
|
||||||
|
let balance = Balance { coin: Coin::Serai, amount: Amount(refund_amount) };
|
||||||
|
Self::transfer_internal(FEE_ACCOUNT.into(), *who, balance)
|
||||||
|
.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Payment))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use pallet::*;
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "serai-tokens-primitives"
|
name = "serai-coins-primitives"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Serai tokens primitives"
|
description = "Serai coins primitives"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
|
@ -10,9 +10,9 @@ use serde::{Serialize, Deserialize};
|
||||||
use scale::{Encode, Decode, MaxEncodedLen};
|
use scale::{Encode, Decode, MaxEncodedLen};
|
||||||
use scale_info::TypeInfo;
|
use scale_info::TypeInfo;
|
||||||
|
|
||||||
use serai_primitives::{Balance, SeraiAddress, ExternalAddress, Data, pallet_address};
|
use serai_primitives::{Balance, SeraiAddress, ExternalAddress, Data, system_address};
|
||||||
|
|
||||||
pub const ADDRESS: SeraiAddress = pallet_address(b"Tokens");
|
pub const FEE_ACCOUNT: SeraiAddress = system_address(b"FeeAccount");
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Encode, Decode, MaxEncodedLen, TypeInfo,
|
Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Encode, Decode, MaxEncodedLen, TypeInfo,
|
||||||
|
@ -44,5 +44,8 @@ pub enum Destination {
|
||||||
#[test]
|
#[test]
|
||||||
fn address() {
|
fn address() {
|
||||||
use sp_runtime::traits::TrailingZeroInput;
|
use sp_runtime::traits::TrailingZeroInput;
|
||||||
assert_eq!(ADDRESS, SeraiAddress::decode(&mut TrailingZeroInput::new(b"Tokens")).unwrap());
|
assert_eq!(
|
||||||
|
FEE_ACCOUNT,
|
||||||
|
SeraiAddress::decode(&mut TrailingZeroInput::new(b"FeeAccount")).unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -28,7 +28,7 @@ frame-support = { git = "https://github.com/serai-dex/substrate", default-featur
|
||||||
serai-primitives = { path = "../../primitives", default-features = false }
|
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 }
|
||||||
|
|
||||||
tokens-pallet = { package = "serai-tokens-pallet", path = "../../tokens/pallet", default-features = false }
|
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/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]
|
||||||
|
@ -49,7 +49,7 @@ std = [
|
||||||
"serai-primitives/std",
|
"serai-primitives/std",
|
||||||
"in-instructions-primitives/std",
|
"in-instructions-primitives/std",
|
||||||
|
|
||||||
"tokens-pallet/std",
|
"coins-pallet/std",
|
||||||
"validator-sets-pallet/std",
|
"validator-sets-pallet/std",
|
||||||
]
|
]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub mod pallet {
|
||||||
use frame_support::pallet_prelude::*;
|
use frame_support::pallet_prelude::*;
|
||||||
use frame_system::pallet_prelude::*;
|
use frame_system::pallet_prelude::*;
|
||||||
|
|
||||||
use tokens_pallet::{Config as TokensConfig, Pallet as Tokens};
|
use coins_pallet::{Config as CoinsConfig, Pallet as Coins};
|
||||||
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 +39,7 @@ pub mod pallet {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[pallet::config]
|
#[pallet::config]
|
||||||
pub trait Config: frame_system::Config + ValidatorSetsConfig + TokensConfig {
|
pub trait Config: frame_system::Config + ValidatorSetsConfig + CoinsConfig {
|
||||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,10 +73,11 @@ pub mod pallet {
|
||||||
impl<T: Config> Pallet<T> {
|
impl<T: Config> Pallet<T> {
|
||||||
fn execute(instruction: InInstructionWithBalance) -> Result<(), ()> {
|
fn execute(instruction: InInstructionWithBalance) -> Result<(), ()> {
|
||||||
match instruction.instruction {
|
match instruction.instruction {
|
||||||
InInstruction::Transfer(address) => Tokens::<T>::mint(address, instruction.balance),
|
InInstruction::Transfer(address) => {
|
||||||
|
Coins::<T>::mint(&address.into(), instruction.balance).map_err(|_| ())
|
||||||
|
}
|
||||||
_ => panic!("unsupported instruction"),
|
_ => panic!("unsupported instruction"),
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ sp-std = { git = "https://github.com/serai-dex/substrate", default-features = fa
|
||||||
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 }
|
||||||
tokens-primitives = { package = "serai-tokens-primitives", path = "../../tokens/primitives", default-features = false }
|
coins-primitives = { package = "serai-coins-primitives", path = "../../coins/primitives", default-features = false }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = [
|
std = [
|
||||||
|
@ -38,6 +38,6 @@ std = [
|
||||||
"sp-runtime/std",
|
"sp-runtime/std",
|
||||||
|
|
||||||
"serai-primitives/std",
|
"serai-primitives/std",
|
||||||
"tokens-primitives/std"
|
"coins-primitives/std"
|
||||||
]
|
]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|
|
@ -8,7 +8,7 @@ use scale_info::TypeInfo;
|
||||||
|
|
||||||
use serai_primitives::{Coin, Amount, SeraiAddress, ExternalAddress};
|
use serai_primitives::{Coin, Amount, SeraiAddress, ExternalAddress};
|
||||||
|
|
||||||
use tokens_primitives::OutInstruction;
|
use coins_primitives::OutInstruction;
|
||||||
|
|
||||||
use crate::RefundableInInstruction;
|
use crate::RefundableInInstruction;
|
||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
|
|
|
@ -5,9 +5,9 @@ use sp_core::Pair as PairTrait;
|
||||||
use sc_service::ChainType;
|
use sc_service::ChainType;
|
||||||
|
|
||||||
use serai_runtime::{
|
use serai_runtime::{
|
||||||
primitives::*, tokens::primitives::ADDRESS as TOKENS_ADDRESS, WASM_BINARY, opaque::SessionKeys,
|
primitives::*, WASM_BINARY, opaque::SessionKeys, BABE_GENESIS_EPOCH_CONFIG, RuntimeGenesisConfig,
|
||||||
BABE_GENESIS_EPOCH_CONFIG, RuntimeGenesisConfig, SystemConfig, BalancesConfig, AssetsConfig,
|
SystemConfig, ValidatorSetsConfig, SessionConfig, BabeConfig, GrandpaConfig,
|
||||||
ValidatorSetsConfig, SessionConfig, BabeConfig, GrandpaConfig, AuthorityDiscoveryConfig,
|
AuthorityDiscoveryConfig, CoinsConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type ChainSpec = sc_service::GenericChainSpec<RuntimeGenesisConfig>;
|
pub type ChainSpec = sc_service::GenericChainSpec<RuntimeGenesisConfig>;
|
||||||
|
@ -34,24 +34,13 @@ fn testnet_genesis(
|
||||||
RuntimeGenesisConfig {
|
RuntimeGenesisConfig {
|
||||||
system: SystemConfig { code: wasm_binary.to_vec(), _config: PhantomData },
|
system: SystemConfig { code: wasm_binary.to_vec(), _config: PhantomData },
|
||||||
|
|
||||||
balances: BalancesConfig {
|
|
||||||
balances: endowed_accounts.into_iter().map(|k| (k, 1 << 60)).collect(),
|
|
||||||
},
|
|
||||||
transaction_payment: Default::default(),
|
transaction_payment: Default::default(),
|
||||||
|
|
||||||
assets: AssetsConfig {
|
coins: CoinsConfig {
|
||||||
assets: [Coin::Bitcoin, Coin::Ether, Coin::Dai, Coin::Monero]
|
accounts: endowed_accounts
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|coin| (*coin, TOKENS_ADDRESS.into(), true, 1))
|
.map(|a| (a, Balance { coin: Coin::Serai, amount: Amount(1 << 60) }))
|
||||||
.collect(),
|
.collect(),
|
||||||
metadata: vec![
|
|
||||||
(Coin::Bitcoin, b"Bitcoin".to_vec(), b"BTC".to_vec(), 8),
|
|
||||||
// Reduce to 8 decimals to feasibly fit within u64 (instead of its native u256)
|
|
||||||
(Coin::Ether, b"Ether".to_vec(), b"ETH".to_vec(), 8),
|
|
||||||
(Coin::Dai, b"Dai Stablecoin".to_vec(), b"DAI".to_vec(), 8),
|
|
||||||
(Coin::Monero, b"Monero".to_vec(), b"XMR".to_vec(), 12),
|
|
||||||
],
|
|
||||||
accounts: vec![],
|
|
||||||
},
|
},
|
||||||
|
|
||||||
validator_sets: ValidatorSetsConfig {
|
validator_sets: ValidatorSetsConfig {
|
||||||
|
|
|
@ -88,7 +88,7 @@ impl StaticLookup for AccountLookup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn pallet_address(pallet: &'static [u8]) -> SeraiAddress {
|
pub const fn system_address(pallet: &'static [u8]) -> SeraiAddress {
|
||||||
let mut address = [0; 32];
|
let mut address = [0; 32];
|
||||||
let mut set = false;
|
let mut set = false;
|
||||||
// Implement a while loop since we can't use a for loop
|
// Implement a while loop since we can't use a for loop
|
||||||
|
|
|
@ -34,6 +34,8 @@ pub enum NetworkId {
|
||||||
pub const NETWORKS: [NetworkId; 4] =
|
pub const NETWORKS: [NetworkId; 4] =
|
||||||
[NetworkId::Serai, NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero];
|
[NetworkId::Serai, NetworkId::Bitcoin, NetworkId::Ethereum, NetworkId::Monero];
|
||||||
|
|
||||||
|
pub const COINS: [Coin; 5] = [Coin::Serai, Coin::Bitcoin, Coin::Ether, Coin::Dai, Coin::Monero];
|
||||||
|
|
||||||
/// The type used to identify coins.
|
/// The type used to identify coins.
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone,
|
Clone,
|
||||||
|
@ -68,6 +70,37 @@ impl Coin {
|
||||||
Coin::Monero => NetworkId::Monero,
|
Coin::Monero => NetworkId::Monero,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Coin::Serai => "Serai",
|
||||||
|
Coin::Bitcoin => "Bitcoin",
|
||||||
|
Coin::Ether => "Ether",
|
||||||
|
Coin::Dai => "Dai Stablecoin",
|
||||||
|
Coin::Monero => "Monero",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn symbol(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Coin::Serai => "SRI",
|
||||||
|
Coin::Bitcoin => "BTC",
|
||||||
|
Coin::Ether => "ETH",
|
||||||
|
Coin::Dai => "DAI",
|
||||||
|
Coin::Monero => "XMR",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decimals(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
Coin::Serai => 8,
|
||||||
|
Coin::Bitcoin => 8,
|
||||||
|
// Ether and DAI have 18 decimals, yet we only track 8 in order to fit them within u64s
|
||||||
|
Coin::Ether => 8,
|
||||||
|
Coin::Dai => 8,
|
||||||
|
Coin::Monero => 12,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Max of 8 coins per network
|
// Max of 8 coins per network
|
||||||
|
|
|
@ -43,11 +43,9 @@ serai-primitives = { path = "../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-balances = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
|
||||||
pallet-assets = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
|
||||||
pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
pallet-transaction-payment = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
||||||
|
|
||||||
tokens-pallet = { package = "serai-tokens-pallet", path = "../tokens/pallet", default-features = false }
|
coins-pallet = { package = "serai-coins-pallet", path = "../coins/pallet", default-features = false }
|
||||||
in-instructions-pallet = { package = "serai-in-instructions-pallet", path = "../in-instructions/pallet", default-features = false }
|
in-instructions-pallet = { package = "serai-in-instructions-pallet", path = "../in-instructions/pallet", default-features = false }
|
||||||
|
|
||||||
staking-pallet = { package = "serai-staking-pallet", path = "../staking/pallet", default-features = false }
|
staking-pallet = { package = "serai-staking-pallet", path = "../staking/pallet", default-features = false }
|
||||||
|
@ -97,11 +95,9 @@ std = [
|
||||||
|
|
||||||
"pallet-timestamp/std",
|
"pallet-timestamp/std",
|
||||||
|
|
||||||
"pallet-balances/std",
|
|
||||||
"pallet-transaction-payment/std",
|
"pallet-transaction-payment/std",
|
||||||
|
|
||||||
"pallet-assets/std",
|
"coins-pallet/std",
|
||||||
"tokens-pallet/std",
|
|
||||||
"in-instructions-pallet/std",
|
"in-instructions-pallet/std",
|
||||||
|
|
||||||
"staking-pallet/std",
|
"staking-pallet/std",
|
||||||
|
@ -126,11 +122,8 @@ runtime-benchmarks = [
|
||||||
|
|
||||||
"pallet-timestamp/runtime-benchmarks",
|
"pallet-timestamp/runtime-benchmarks",
|
||||||
|
|
||||||
"pallet-balances/runtime-benchmarks",
|
"pallet-babe/runtime-benchmarks",
|
||||||
"pallet-assets/runtime-benchmarks",
|
"pallet-grandpa/runtime-benchmarks",
|
||||||
|
|
||||||
"pallet-babe/runtime-benchmarks",
|
|
||||||
"pallet-grandpa/runtime-benchmarks",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
|
|
|
@ -14,11 +14,9 @@ pub use frame_support as support;
|
||||||
|
|
||||||
pub use pallet_timestamp as timestamp;
|
pub use pallet_timestamp as timestamp;
|
||||||
|
|
||||||
pub use pallet_balances as balances;
|
|
||||||
pub use pallet_transaction_payment as transaction_payment;
|
pub use pallet_transaction_payment as transaction_payment;
|
||||||
|
|
||||||
pub use pallet_assets as assets;
|
pub use coins_pallet as coins;
|
||||||
pub use tokens_pallet as tokens;
|
|
||||||
pub use in_instructions_pallet as in_instructions;
|
pub use in_instructions_pallet as in_instructions;
|
||||||
|
|
||||||
pub use staking_pallet as staking;
|
pub use staking_pallet as staking;
|
||||||
|
@ -45,10 +43,10 @@ use sp_runtime::{
|
||||||
ApplyExtrinsicResult, Perbill,
|
ApplyExtrinsicResult, Perbill,
|
||||||
};
|
};
|
||||||
|
|
||||||
use primitives::{PublicKey, SeraiAddress, AccountLookup, Signature, SubstrateAmount, Coin};
|
use primitives::{PublicKey, SeraiAddress, AccountLookup, Signature, SubstrateAmount};
|
||||||
|
|
||||||
use support::{
|
use support::{
|
||||||
traits::{ConstU8, ConstU32, ConstU64, Contains},
|
traits::{ConstU8, ConstU64, Contains},
|
||||||
weights::{
|
weights::{
|
||||||
constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND},
|
constants::{RocksDbWeight, WEIGHT_REF_TIME_PER_SECOND},
|
||||||
IdentityFee, Weight,
|
IdentityFee, Weight,
|
||||||
|
@ -56,8 +54,6 @@ use support::{
|
||||||
parameter_types, construct_runtime,
|
parameter_types, construct_runtime,
|
||||||
};
|
};
|
||||||
|
|
||||||
use transaction_payment::CurrencyAdapter;
|
|
||||||
|
|
||||||
use babe::AuthorityId as BabeId;
|
use babe::AuthorityId as BabeId;
|
||||||
use grandpa::AuthorityId as GrandpaId;
|
use grandpa::AuthorityId as GrandpaId;
|
||||||
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
|
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
|
||||||
|
@ -153,22 +149,10 @@ impl Contains<RuntimeCall> for CallFilter {
|
||||||
return matches!(call, timestamp::Call::set { .. });
|
return matches!(call, timestamp::Call::set { .. });
|
||||||
}
|
}
|
||||||
|
|
||||||
if let RuntimeCall::Balances(call) = call {
|
if let RuntimeCall::Coins(call) = call {
|
||||||
return matches!(call, balances::Call::transfer { .. } | balances::Call::transfer_all { .. });
|
return matches!(call, coins::Call::transfer { .. } | coins::Call::burn { .. });
|
||||||
}
|
}
|
||||||
|
|
||||||
if let RuntimeCall::Assets(call) = call {
|
|
||||||
return matches!(
|
|
||||||
call,
|
|
||||||
assets::Call::approve_transfer { .. } |
|
|
||||||
assets::Call::cancel_approval { .. } |
|
|
||||||
assets::Call::transfer { .. } |
|
|
||||||
assets::Call::transfer_approved { .. }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let RuntimeCall::Tokens(call) = call {
|
|
||||||
return matches!(call, tokens::Call::burn { .. });
|
|
||||||
}
|
|
||||||
if let RuntimeCall::InInstructions(call) = call {
|
if let RuntimeCall::InInstructions(call) = call {
|
||||||
return matches!(call, in_instructions::Call::execute_batch { .. });
|
return matches!(call, in_instructions::Call::execute_batch { .. });
|
||||||
}
|
}
|
||||||
|
@ -217,7 +201,7 @@ impl system::Config for Runtime {
|
||||||
type OnKilledAccount = ();
|
type OnKilledAccount = ();
|
||||||
type OnSetCode = ();
|
type OnSetCode = ();
|
||||||
|
|
||||||
type AccountData = balances::AccountData<SubstrateAmount>;
|
type AccountData = ();
|
||||||
type SystemWeightInfo = ();
|
type SystemWeightInfo = ();
|
||||||
type SS58Prefix = SS58Prefix; // TODO: Remove for Bech32m
|
type SS58Prefix = SS58Prefix; // TODO: Remove for Bech32m
|
||||||
|
|
||||||
|
@ -231,83 +215,16 @@ impl timestamp::Config for Runtime {
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl balances::Config for Runtime {
|
|
||||||
type RuntimeEvent = RuntimeEvent;
|
|
||||||
|
|
||||||
type Balance = SubstrateAmount;
|
|
||||||
|
|
||||||
type ReserveIdentifier = ();
|
|
||||||
type FreezeIdentifier = ();
|
|
||||||
type RuntimeHoldReason = ();
|
|
||||||
|
|
||||||
type MaxLocks = ();
|
|
||||||
type MaxReserves = ();
|
|
||||||
type MaxHolds = ();
|
|
||||||
type MaxFreezes = ();
|
|
||||||
|
|
||||||
type DustRemoval = ();
|
|
||||||
type ExistentialDeposit = ConstU64<1>;
|
|
||||||
// TODO: What's the benefit to this?
|
|
||||||
type AccountStore = System;
|
|
||||||
type WeightInfo = balances::weights::SubstrateWeight<Runtime>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl transaction_payment::Config for Runtime {
|
impl transaction_payment::Config for Runtime {
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
type OnChargeTransaction = CurrencyAdapter<Balances, ()>;
|
type OnChargeTransaction = Coins;
|
||||||
type OperationalFeeMultiplier = ConstU8<5>;
|
type OperationalFeeMultiplier = ConstU8<5>;
|
||||||
type WeightToFee = IdentityFee<SubstrateAmount>;
|
type WeightToFee = IdentityFee<SubstrateAmount>;
|
||||||
type LengthToFee = IdentityFee<SubstrateAmount>;
|
type LengthToFee = IdentityFee<SubstrateAmount>;
|
||||||
type FeeMultiplierUpdate = ();
|
type FeeMultiplierUpdate = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "runtime-benchmarks")]
|
impl coins::Config for Runtime {
|
||||||
pub struct SeraiAssetBenchmarkHelper;
|
|
||||||
#[cfg(feature = "runtime-benchmarks")]
|
|
||||||
impl assets::BenchmarkHelper<Coin> for SeraiAssetBenchmarkHelper {
|
|
||||||
fn create_asset_id_parameter(id: u32) -> Coin {
|
|
||||||
match (id % 4) + 1 {
|
|
||||||
1 => Coin::Bitcoin,
|
|
||||||
2 => Coin::Ether,
|
|
||||||
3 => Coin::Dai,
|
|
||||||
4 => Coin::Monero,
|
|
||||||
_ => panic!("(id % 4) + 1 wasn't in [1, 4]"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl assets::Config for Runtime {
|
|
||||||
type RuntimeEvent = RuntimeEvent;
|
|
||||||
type Balance = SubstrateAmount;
|
|
||||||
type Currency = Balances;
|
|
||||||
|
|
||||||
type AssetId = Coin;
|
|
||||||
type AssetIdParameter = Coin;
|
|
||||||
type StringLimit = ConstU32<32>;
|
|
||||||
|
|
||||||
// Don't allow anyone to create assets
|
|
||||||
type CreateOrigin = support::traits::AsEnsureOriginWithArg<system::EnsureNever<PublicKey>>;
|
|
||||||
type ForceOrigin = system::EnsureRoot<PublicKey>;
|
|
||||||
|
|
||||||
// Don't charge fees nor kill accounts
|
|
||||||
type RemoveItemsLimit = ConstU32<0>;
|
|
||||||
type AssetDeposit = ConstU64<0>;
|
|
||||||
type AssetAccountDeposit = ConstU64<0>;
|
|
||||||
type MetadataDepositBase = ConstU64<0>;
|
|
||||||
type MetadataDepositPerByte = ConstU64<0>;
|
|
||||||
type ApprovalDeposit = ConstU64<0>;
|
|
||||||
|
|
||||||
// Unused hooks
|
|
||||||
type CallbackHandle = ();
|
|
||||||
type Freezer = ();
|
|
||||||
type Extra = ();
|
|
||||||
|
|
||||||
type WeightInfo = assets::weights::SubstrateWeight<Runtime>;
|
|
||||||
#[cfg(feature = "runtime-benchmarks")]
|
|
||||||
type BenchmarkHelper = SeraiAssetBenchmarkHelper;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl tokens::Config for Runtime {
|
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,9 +232,7 @@ impl in_instructions::Config for Runtime {
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl staking::Config for Runtime {
|
impl staking::Config for Runtime {}
|
||||||
type Currency = Balances;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl validator_sets::Config for Runtime {
|
impl validator_sets::Config for Runtime {
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
|
@ -403,11 +318,9 @@ construct_runtime!(
|
||||||
|
|
||||||
Timestamp: timestamp,
|
Timestamp: timestamp,
|
||||||
|
|
||||||
Balances: balances,
|
|
||||||
TransactionPayment: transaction_payment,
|
TransactionPayment: transaction_payment,
|
||||||
|
|
||||||
Assets: assets,
|
Coins: coins,
|
||||||
Tokens: tokens,
|
|
||||||
InInstructions: in_instructions,
|
InInstructions: in_instructions,
|
||||||
|
|
||||||
ValidatorSets: validator_sets,
|
ValidatorSets: validator_sets,
|
||||||
|
|
|
@ -15,17 +15,19 @@ rustdoc-args = ["--cfg", "docsrs"]
|
||||||
parity-scale-codec = { version = "3", default-features = false, features = ["derive"] }
|
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-runtime = { 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-runtime = { 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 }
|
||||||
|
|
||||||
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 }
|
|
||||||
|
|
||||||
serai-primitives = { path = "../../primitives", default-features = false }
|
serai-primitives = { path = "../../primitives", default-features = false }
|
||||||
serai-validator-sets-primitives = { path = "../../validator-sets/primitives", default-features = false }
|
|
||||||
|
coins-pallet = { package = "serai-coins-pallet", path = "../../coins/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 }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
std = [
|
std = [
|
||||||
|
@ -33,8 +35,14 @@ std = [
|
||||||
"frame-support/std",
|
"frame-support/std",
|
||||||
|
|
||||||
"sp-std/std",
|
"sp-std/std",
|
||||||
|
"sp-runtime/std",
|
||||||
|
|
||||||
|
"serai-primitives/std",
|
||||||
|
|
||||||
|
"coins-pallet/std",
|
||||||
|
|
||||||
"validator-sets-pallet/std",
|
"validator-sets-pallet/std",
|
||||||
|
|
||||||
"pallet-session/std",
|
"pallet-session/std",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -6,12 +6,11 @@ pub mod pallet {
|
||||||
use sp_std::vec::Vec;
|
use sp_std::vec::Vec;
|
||||||
|
|
||||||
use frame_system::pallet_prelude::*;
|
use frame_system::pallet_prelude::*;
|
||||||
use frame_support::{
|
use frame_support::pallet_prelude::*;
|
||||||
pallet_prelude::*,
|
|
||||||
traits::{Currency, tokens::ExistenceRequirement},
|
|
||||||
};
|
|
||||||
|
|
||||||
use serai_primitives::{NetworkId, Amount, PublicKey};
|
use serai_primitives::*;
|
||||||
|
|
||||||
|
use coins_pallet::{Config as CoinsConfig, Pallet as Coins};
|
||||||
|
|
||||||
use validator_sets_pallet::{
|
use validator_sets_pallet::{
|
||||||
primitives::{Session, ValidatorSet},
|
primitives::{Session, ValidatorSet},
|
||||||
|
@ -29,9 +28,8 @@ pub mod pallet {
|
||||||
|
|
||||||
#[pallet::config]
|
#[pallet::config]
|
||||||
pub trait Config:
|
pub trait Config:
|
||||||
frame_system::Config + VsConfig + SessionConfig<ValidatorId = PublicKey>
|
frame_system::Config + CoinsConfig + VsConfig + SessionConfig<ValidatorId = PublicKey>
|
||||||
{
|
{
|
||||||
type Currency: Currency<Self::AccountId, Balance = u64>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pallet::pallet]
|
#[pallet::pallet]
|
||||||
|
@ -98,10 +96,8 @@ pub mod pallet {
|
||||||
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
#[pallet::weight((0, DispatchClass::Operational))] // TODO
|
||||||
pub fn stake(origin: OriginFor<T>, #[pallet::compact] amount: u64) -> DispatchResult {
|
pub fn stake(origin: OriginFor<T>, #[pallet::compact] amount: u64) -> DispatchResult {
|
||||||
let signer = ensure_signed(origin)?;
|
let signer = ensure_signed(origin)?;
|
||||||
// Serai accounts are solely public keys. Accordingly, there's no harm to letting accounts
|
let balance = Balance { coin: Coin::Serai, amount: Amount(amount) };
|
||||||
// die. They'll simply be re-instantiated later
|
Coins::<T>::transfer_internal(&signer, &Self::account(), balance)?;
|
||||||
// AllowDeath accordingly to not add additional requirements (and therefore annoyances)
|
|
||||||
T::Currency::transfer(&signer, &Self::account(), amount, ExistenceRequirement::AllowDeath)?;
|
|
||||||
Self::add_stake(&signer, amount);
|
Self::add_stake(&signer, amount);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -112,8 +108,8 @@ pub mod pallet {
|
||||||
pub fn unstake(origin: OriginFor<T>, #[pallet::compact] amount: u64) -> DispatchResult {
|
pub fn unstake(origin: OriginFor<T>, #[pallet::compact] amount: u64) -> DispatchResult {
|
||||||
let signer = ensure_signed(origin)?;
|
let signer = ensure_signed(origin)?;
|
||||||
Self::remove_stake(&signer, amount)?;
|
Self::remove_stake(&signer, amount)?;
|
||||||
// This should never be out of funds as there should always be stakers. Accordingly...
|
let balance = Balance { coin: Coin::Serai, amount: Amount(amount) };
|
||||||
T::Currency::transfer(&Self::account(), &signer, amount, ExistenceRequirement::KeepAlive)?;
|
Coins::<T>::transfer_internal(&Self::account(), &signer, balance)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "serai-tokens-pallet"
|
|
||||||
version = "0.1.0"
|
|
||||||
description = "Mint and burn Serai tokens"
|
|
||||||
license = "AGPL-3.0-only"
|
|
||||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
|
||||||
edition = "2021"
|
|
||||||
publish = false
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
all-features = true
|
|
||||||
rustdoc-args = ["--cfg", "docsrs"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive", "max-encoded-len"] }
|
|
||||||
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 }
|
|
||||||
|
|
||||||
pallet-assets = { git = "https://github.com/serai-dex/substrate", default-features = false }
|
|
||||||
|
|
||||||
serai-primitives = { path = "../../primitives", default-features = false }
|
|
||||||
tokens-primitives = { package = "serai-tokens-primitives", path = "../primitives", default-features = false }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
std = [
|
|
||||||
"scale/std",
|
|
||||||
"scale-info/std",
|
|
||||||
|
|
||||||
"frame-system/std",
|
|
||||||
"frame-support/std",
|
|
||||||
|
|
||||||
"pallet-assets/std",
|
|
||||||
|
|
||||||
"serai-primitives/std",
|
|
||||||
]
|
|
||||||
default = ["std"]
|
|
|
@ -1,82 +0,0 @@
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
||||||
#![cfg_attr(not(feature = "std"), no_std)]
|
|
||||||
|
|
||||||
pub use tokens_primitives as primitives;
|
|
||||||
|
|
||||||
#[frame_support::pallet]
|
|
||||||
pub mod pallet {
|
|
||||||
use frame_support::pallet_prelude::*;
|
|
||||||
use frame_system::{pallet_prelude::*, RawOrigin};
|
|
||||||
|
|
||||||
use pallet_assets::{Config as AssetsConfig, Pallet as AssetsPallet};
|
|
||||||
|
|
||||||
use serai_primitives::{SubstrateAmount, Coin, Balance, PublicKey, SeraiAddress, AccountLookup};
|
|
||||||
use primitives::{ADDRESS, OutInstruction};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[pallet::config]
|
|
||||||
pub trait Config:
|
|
||||||
frame_system::Config<AccountId = PublicKey, Lookup = AccountLookup>
|
|
||||||
+ AssetsConfig<AssetIdParameter = Coin, Balance = SubstrateAmount>
|
|
||||||
{
|
|
||||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pallet::event]
|
|
||||||
#[pallet::generate_deposit(fn deposit_event)]
|
|
||||||
pub enum Event<T: Config> {
|
|
||||||
// Mint is technically redundant as the assets pallet has the exact same event already
|
|
||||||
// Providing our own definition here just helps consolidate code
|
|
||||||
Mint { address: SeraiAddress, balance: Balance },
|
|
||||||
Burn { address: SeraiAddress, balance: Balance, instruction: OutInstruction },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pallet::pallet]
|
|
||||||
pub struct Pallet<T>(PhantomData<T>);
|
|
||||||
|
|
||||||
impl<T: Config> Pallet<T> {
|
|
||||||
fn burn_internal(
|
|
||||||
address: SeraiAddress,
|
|
||||||
balance: Balance,
|
|
||||||
instruction: OutInstruction,
|
|
||||||
) -> DispatchResult {
|
|
||||||
AssetsPallet::<T>::burn(
|
|
||||||
RawOrigin::Signed(ADDRESS.into()).into(),
|
|
||||||
balance.coin,
|
|
||||||
address,
|
|
||||||
balance.amount.0,
|
|
||||||
)?;
|
|
||||||
Pallet::<T>::deposit_event(Event::Burn { address, balance, instruction });
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mint(address: SeraiAddress, balance: Balance) {
|
|
||||||
// TODO: Prevent minting when it'd cause an amount exceeding the allocated stake
|
|
||||||
AssetsPallet::<T>::mint(
|
|
||||||
RawOrigin::Signed(ADDRESS.into()).into(),
|
|
||||||
balance.coin,
|
|
||||||
address,
|
|
||||||
balance.amount.0,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
Pallet::<T>::deposit_event(Event::Mint { address, balance });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[pallet::call]
|
|
||||||
impl<T: Config> Pallet<T> {
|
|
||||||
#[pallet::call_index(0)]
|
|
||||||
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
|
||||||
pub fn burn(
|
|
||||||
origin: OriginFor<T>,
|
|
||||||
balance: Balance,
|
|
||||||
instruction: OutInstruction,
|
|
||||||
) -> DispatchResult {
|
|
||||||
Self::burn_internal(ensure_signed(origin)?.into(), balance, instruction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use pallet::*;
|
|
|
@ -19,7 +19,7 @@ use serai_client::{
|
||||||
},
|
},
|
||||||
coins::{
|
coins::{
|
||||||
primitives::{OutInstruction, OutInstructionWithBalance},
|
primitives::{OutInstruction, OutInstructionWithBalance},
|
||||||
TokensEvent,
|
CoinsEvent,
|
||||||
},
|
},
|
||||||
in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch},
|
in_instructions::primitives::{InInstruction, InInstructionWithBalance, Batch},
|
||||||
SeraiCoins,
|
SeraiCoins,
|
||||||
|
@ -206,12 +206,13 @@ async fn sign_test() {
|
||||||
let address = SeraiAddress::from(pair.public());
|
let address = SeraiAddress::from(pair.public());
|
||||||
|
|
||||||
// Fund the new account to pay for fees
|
// Fund the new account to pay for fees
|
||||||
|
let balance = Balance { coin: Coin::Serai, amount: Amount(1_000_000_000) };
|
||||||
serai
|
serai
|
||||||
.publish(
|
.publish(
|
||||||
&serai
|
&serai
|
||||||
.sign(
|
.sign(
|
||||||
&PairSigner::new(insecure_pair_from_name("Ferdie")),
|
&PairSigner::new(insecure_pair_from_name("Ferdie")),
|
||||||
&SeraiCoins::transfer_sri(address, Amount(1_000_000_000)),
|
&SeraiCoins::transfer(address, balance),
|
||||||
0,
|
0,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
)
|
)
|
||||||
|
@ -249,29 +250,32 @@ async fn sign_test() {
|
||||||
serai.block_by_number(block_included_in).await.unwrap().unwrap().hash();
|
serai.block_by_number(block_included_in).await.unwrap().unwrap().hash();
|
||||||
|
|
||||||
let serai = serai.as_of(block_included_in_hash).coins();
|
let serai = serai.as_of(block_included_in_hash).coins();
|
||||||
assert_eq!(serai.sri_balance(serai_addr).await.unwrap(), 1_000_000_000);
|
assert_eq!(
|
||||||
|
serai.coin_balance(Coin::Serai, serai_addr).await.unwrap(),
|
||||||
|
Amount(1_000_000_000)
|
||||||
|
);
|
||||||
|
|
||||||
// Verify the mint occurred as expected
|
// Verify the mint occurred as expected
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
serai.mint_events().await.unwrap(),
|
serai.mint_events().await.unwrap(),
|
||||||
vec![TokensEvent::Mint { address: serai_addr, balance }]
|
vec![CoinsEvent::Mint { address: serai_addr, balance }]
|
||||||
);
|
);
|
||||||
assert_eq!(serai.token_supply(Coin::Bitcoin).await.unwrap(), amount);
|
assert_eq!(serai.coin_supply(Coin::Bitcoin).await.unwrap(), amount);
|
||||||
assert_eq!(serai.token_balance(Coin::Bitcoin, serai_addr).await.unwrap(), amount);
|
assert_eq!(serai.coin_balance(Coin::Bitcoin, serai_addr).await.unwrap(), amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger a burn
|
// Trigger a burn
|
||||||
let out_instruction =
|
let out_instruction = OutInstructionWithBalance {
|
||||||
OutInstruction { address: ExternalAddress::new(b"external".to_vec()).unwrap(), data: None };
|
balance,
|
||||||
|
instruction: OutInstruction {
|
||||||
|
address: ExternalAddress::new(b"external".to_vec()).unwrap(),
|
||||||
|
data: None,
|
||||||
|
},
|
||||||
|
};
|
||||||
serai
|
serai
|
||||||
.publish(
|
.publish(
|
||||||
&serai
|
&serai
|
||||||
.sign(
|
.sign(&serai_pair, &SeraiCoins::burn(out_instruction.clone()), 0, Default::default())
|
||||||
&serai_pair,
|
|
||||||
&SeraiCoins::burn(balance, out_instruction.clone()),
|
|
||||||
0,
|
|
||||||
Default::default(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -297,11 +301,7 @@ async fn sign_test() {
|
||||||
assert_eq!(burn_events.len(), 1);
|
assert_eq!(burn_events.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
burn_events[0],
|
burn_events[0],
|
||||||
TokensEvent::Burn {
|
CoinsEvent::Burn { address: serai_addr, instruction: out_instruction.clone() }
|
||||||
address: serai_addr,
|
|
||||||
balance,
|
|
||||||
instruction: out_instruction.clone()
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
break 'outer;
|
break 'outer;
|
||||||
}
|
}
|
||||||
|
@ -312,8 +312,8 @@ async fn sign_test() {
|
||||||
let last_serai_block = serai.block_by_number(last_serai_block).await.unwrap().unwrap();
|
let last_serai_block = serai.block_by_number(last_serai_block).await.unwrap().unwrap();
|
||||||
let last_serai_block_hash = last_serai_block.hash();
|
let last_serai_block_hash = last_serai_block.hash();
|
||||||
let serai = serai.as_of(last_serai_block_hash).coins();
|
let serai = serai.as_of(last_serai_block_hash).coins();
|
||||||
assert_eq!(serai.token_supply(Coin::Bitcoin).await.unwrap(), Amount(0));
|
assert_eq!(serai.coin_supply(Coin::Bitcoin).await.unwrap(), Amount(0));
|
||||||
assert_eq!(serai.token_balance(Coin::Bitcoin, serai_addr).await.unwrap(), Amount(0));
|
assert_eq!(serai.coin_balance(Coin::Bitcoin, serai_addr).await.unwrap(), Amount(0));
|
||||||
|
|
||||||
let mut plan_id = [0; 32];
|
let mut plan_id = [0; 32];
|
||||||
OsRng.fill_bytes(&mut plan_id);
|
OsRng.fill_bytes(&mut plan_id);
|
||||||
|
@ -331,10 +331,7 @@ async fn sign_test() {
|
||||||
},
|
},
|
||||||
network: NetworkId::Bitcoin,
|
network: NetworkId::Bitcoin,
|
||||||
block: last_serai_block.number(),
|
block: last_serai_block.number(),
|
||||||
burns: vec![OutInstructionWithBalance {
|
burns: vec![out_instruction.clone()],
|
||||||
instruction: out_instruction.clone(),
|
|
||||||
balance: Balance { coin: Coin::Bitcoin, amount }
|
|
||||||
}],
|
|
||||||
batches: vec![],
|
batches: vec![],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,7 +15,7 @@ use serai_client::{
|
||||||
},
|
},
|
||||||
validator_sets::primitives::{Session, ValidatorSet},
|
validator_sets::primitives::{Session, ValidatorSet},
|
||||||
in_instructions::primitives::Shorthand,
|
in_instructions::primitives::Shorthand,
|
||||||
coins::primitives::OutInstruction,
|
coins::primitives::{OutInstruction, OutInstructionWithBalance},
|
||||||
PairTrait, PairSigner, SeraiCoins,
|
PairTrait, PairSigner, SeraiCoins,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -236,12 +236,13 @@ async fn mint_and_burn_test() {
|
||||||
let address = SeraiAddress::from(pair.public());
|
let address = SeraiAddress::from(pair.public());
|
||||||
|
|
||||||
// Fund the new account to pay for fees
|
// Fund the new account to pay for fees
|
||||||
|
let balance = Balance { coin: Coin::Serai, amount: Amount(1_000_000_000) };
|
||||||
serai
|
serai
|
||||||
.publish(
|
.publish(
|
||||||
&serai
|
&serai
|
||||||
.sign(
|
.sign(
|
||||||
&PairSigner::new(insecure_pair_from_name("Ferdie")),
|
&PairSigner::new(insecure_pair_from_name("Ferdie")),
|
||||||
&SeraiCoins::transfer_sri(address, Amount(1_000_000_000)),
|
&SeraiCoins::transfer(address, balance),
|
||||||
0,
|
0,
|
||||||
Default::default(),
|
Default::default(),
|
||||||
)
|
)
|
||||||
|
@ -488,17 +489,15 @@ async fn mint_and_burn_test() {
|
||||||
let serai = &serai;
|
let serai = &serai;
|
||||||
let serai_pair = &serai_pair;
|
let serai_pair = &serai_pair;
|
||||||
move |nonce, coin, amount, address| async move {
|
move |nonce, coin, amount, address| async move {
|
||||||
let out_instruction = OutInstruction { address, data: None };
|
let out_instruction = OutInstructionWithBalance {
|
||||||
|
balance: Balance { coin, amount: Amount(amount) },
|
||||||
|
instruction: OutInstruction { address, data: None },
|
||||||
|
};
|
||||||
|
|
||||||
serai
|
serai
|
||||||
.publish(
|
.publish(
|
||||||
&serai
|
&serai
|
||||||
.sign(
|
.sign(serai_pair, &SeraiCoins::burn(out_instruction), nonce, Default::default())
|
||||||
serai_pair,
|
|
||||||
&SeraiCoins::burn(Balance { coin, amount: Amount(amount) }, out_instruction),
|
|
||||||
nonce,
|
|
||||||
Default::default(),
|
|
||||||
)
|
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
Loading…
Reference in a new issue