diff --git a/Cargo.lock b/Cargo.lock index 0fc29547..e5cdebfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8195,6 +8195,7 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", "scale-info", + "serai-abi", "serai-coins-pallet", "serai-dex-pallet", "serai-in-instructions-pallet", diff --git a/coordinator/src/tributary/scanner.rs b/coordinator/src/tributary/scanner.rs index 25c8b5c2..9b56e0a0 100644 --- a/coordinator/src/tributary/scanner.rs +++ b/coordinator/src/tributary/scanner.rs @@ -133,7 +133,13 @@ mod impl_pst_for_serai { key_pair: KeyPair, signature: Signature, ) { - let tx = SeraiValidatorSets::set_keys(set.network, removed, key_pair, signature); + // TODO: BoundedVec as an arg to avoid this expect + let tx = SeraiValidatorSets::set_keys( + set.network, + removed.try_into().expect("removing more than allowed"), + key_pair, + signature, + ); async fn check(serai: SeraiValidatorSets<'_>, set: ValidatorSet, (): ()) -> bool { if matches!(serai.keys(set).await, Ok(Some(_))) { log::info!("another coordinator set key pair for {:?}", set); diff --git a/substrate/abi/Cargo.toml b/substrate/abi/Cargo.toml index 3aac979a..547761d8 100644 --- a/substrate/abi/Cargo.toml +++ b/substrate/abi/Cargo.toml @@ -16,27 +16,48 @@ rustdoc-args = ["--cfg", "docsrs"] workspace = true [dependencies] -scale = { package = "parity-scale-codec", version = "3", features = ["derive"] } -scale-info = { version = "2", features = ["derive"] } +scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } +scale-info = { version = "2", default-features = false, features = ["derive"] } -borsh = { version = "1", features = ["derive", "de_strict_order"], optional = true } -serde = { version = "1", features = ["derive", "alloc"], optional = true } +borsh = { version = "1", default-features = false, features = ["derive", "de_strict_order"], optional = true } +serde = { version = "1", default-features = false, features = ["derive", "alloc"], optional = true } -sp-core = { git = "https://github.com/serai-dex/substrate" } -sp-runtime = { git = "https://github.com/serai-dex/substrate" } +sp-core = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-runtime = { git = "https://github.com/serai-dex/substrate", default-features = false } -sp-consensus-babe = { git = "https://github.com/serai-dex/substrate" } -sp-consensus-grandpa = { git = "https://github.com/serai-dex/substrate" } +sp-consensus-babe = { git = "https://github.com/serai-dex/substrate", default-features = false } +sp-consensus-grandpa = { git = "https://github.com/serai-dex/substrate", default-features = false } -serai-primitives = { path = "../primitives", version = "0.1" } -serai-coins-primitives = { path = "../coins/primitives", version = "0.1" } -serai-validator-sets-primitives = { path = "../validator-sets/primitives", version = "0.1" } -serai-in-instructions-primitives = { path = "../in-instructions/primitives", version = "0.1" } -serai-signals-primitives = { path = "../signals/primitives", version = "0.1" } +frame-support = { git = "https://github.com/serai-dex/substrate", default-features = false } -frame-support = { git = "https://github.com/serai-dex/substrate" } +serai-primitives = { path = "../primitives", version = "0.1", default-features = false } +serai-coins-primitives = { path = "../coins/primitives", version = "0.1", default-features = false } +serai-validator-sets-primitives = { path = "../validator-sets/primitives", version = "0.1", default-features = false } +serai-in-instructions-primitives = { path = "../in-instructions/primitives", version = "0.1", default-features = false } +serai-signals-primitives = { path = "../signals/primitives", version = "0.1", default-features = false } [features] +std = [ + "scale/std", + "scale-info/std", + + "borsh?/std", + "serde?/std", + + "sp-core/std", + "sp-runtime/std", + + "sp-consensus-babe/std", + "sp-consensus-grandpa/std", + + "frame-support/std", + + "serai-primitives/std", + "serai-coins-primitives/std", + "serai-validator-sets-primitives/std", + "serai-in-instructions-primitives/std", + "serai-signals-primitives/std", +] borsh = [ "dep:borsh", "serai-primitives/borsh", @@ -53,3 +74,4 @@ serde = [ "serai-in-instructions-primitives/serde", "serai-signals-primitives/serde", ] +default = ["std"] diff --git a/substrate/abi/src/babe.rs b/substrate/abi/src/babe.rs index 29bbee9c..9bba63d9 100644 --- a/substrate/abi/src/babe.rs +++ b/substrate/abi/src/babe.rs @@ -4,7 +4,7 @@ use serai_primitives::{Header, SeraiAddress}; #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] pub struct ReportEquivocation { - pub equivocation_proof: Box>, + pub equivocation_proof: alloc::boxed::Box>, pub key_owner_proof: SeraiAddress, } diff --git a/substrate/abi/src/coins.rs b/substrate/abi/src/coins.rs index c3fa2dad..56255b0a 100644 --- a/substrate/abi/src/coins.rs +++ b/substrate/abi/src/coins.rs @@ -5,7 +5,8 @@ use primitives::OutInstructionWithBalance; #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] #[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] pub enum Call { transfer { to: SeraiAddress, balance: Balance }, burn { balance: Balance }, @@ -14,7 +15,17 @@ pub enum Call { #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] #[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] +pub enum LiquidityTokensCall { + transfer { to: SeraiAddress, balance: Balance }, + burn { balance: Balance }, +} + +#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] +#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] pub enum Event { Mint { to: SeraiAddress, balance: Balance }, Burn { from: SeraiAddress, balance: Balance }, diff --git a/substrate/abi/src/dex.rs b/substrate/abi/src/dex.rs index 5136e974..2daa62f0 100644 --- a/substrate/abi/src/dex.rs +++ b/substrate/abi/src/dex.rs @@ -6,7 +6,8 @@ type PoolId = Coin; type MaxSwapPathLength = sp_core::ConstU32<3>; #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] pub enum Call { add_liquidity { coin: Coin, @@ -38,7 +39,8 @@ pub enum Call { } #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] pub enum Event { PoolCreated { pool_id: PoolId, diff --git a/substrate/abi/src/grandpa.rs b/substrate/abi/src/grandpa.rs index 54de8182..ead8dfc2 100644 --- a/substrate/abi/src/grandpa.rs +++ b/substrate/abi/src/grandpa.rs @@ -1,10 +1,11 @@ +use sp_core::{ConstU32, bounded::BoundedVec}; use sp_consensus_grandpa::EquivocationProof; use serai_primitives::{BlockNumber, SeraiAddress}; #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] pub struct ReportEquivocation { - pub equivocation_proof: Box>, + pub equivocation_proof: alloc::boxed::Box>, pub key_owner_proof: SeraiAddress, } @@ -15,10 +16,10 @@ pub enum Call { } #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] -#[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] pub enum Event { - NewAuthorities { authority_set: Vec<(SeraiAddress, u64)> }, + NewAuthorities { authority_set: BoundedVec<(SeraiAddress, u64), ConstU32<0>> }, // TODO: Remove these Paused, Resumed, diff --git a/substrate/abi/src/in_instructions.rs b/substrate/abi/src/in_instructions.rs index 1e5d1bb5..d3ab5ca3 100644 --- a/substrate/abi/src/in_instructions.rs +++ b/substrate/abi/src/in_instructions.rs @@ -5,14 +5,16 @@ use primitives::SignedBatch; #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] #[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] pub enum Call { execute_batch { batch: SignedBatch }, } #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] #[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] pub enum Event { Batch { network: NetworkId, id: u32, block: BlockHash, instructions_hash: [u8; 32] }, InstructionFailure { network: NetworkId, id: u32, index: u32 }, diff --git a/substrate/abi/src/lib.rs b/substrate/abi/src/lib.rs index 2e873f4c..2670bef7 100644 --- a/substrate/abi/src/lib.rs +++ b/substrate/abi/src/lib.rs @@ -1,5 +1,12 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] #![allow(non_camel_case_types)] +extern crate alloc; + +pub use serai_primitives as primitives; + pub mod system; pub mod timestamp; @@ -14,15 +21,13 @@ pub mod signals; pub mod babe; pub mod grandpa; -pub use serai_primitives as primitives; +pub mod tx; #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] pub enum Call { - System, Timestamp(timestamp::Call), - TransactionPayment, Coins(coins::Call), - LiquidityTokens(coins::Call), + LiquidityTokens(coins::LiquidityTokensCall), Dex(dex::Call), ValidatorSets(validator_sets::Call), InInstructions(in_instructions::Call), @@ -53,16 +58,20 @@ pub enum Event { } #[derive(Clone, Copy, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] pub struct Extra { pub era: sp_runtime::generic::Era, - pub nonce: scale::Compact, - pub tip: scale::Compact, + #[codec(compact)] + pub nonce: u32, + #[codec(compact)] + pub tip: u64, } #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] #[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] pub struct SignedPayloadExtra { pub spec_version: u32, pub tx_version: u32, @@ -70,4 +79,4 @@ pub struct SignedPayloadExtra { pub mortality_checkpoint: [u8; 32], } -pub type Transaction = primitives::Transaction; +pub type Transaction = tx::Transaction; diff --git a/substrate/abi/src/signals.rs b/substrate/abi/src/signals.rs index 2c8dd545..6a77672f 100644 --- a/substrate/abi/src/signals.rs +++ b/substrate/abi/src/signals.rs @@ -7,7 +7,8 @@ use primitives::SignalId; #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] #[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] pub enum Call { register_retirement_signal { in_favor_of: [u8; 32] }, revoke_retirement_signal { retirement_signal_id: [u8; 32] }, @@ -18,7 +19,8 @@ pub enum Call { #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] #[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] pub enum Event { RetirementSignalRegistered { signal_id: [u8; 32], diff --git a/substrate/abi/src/system.rs b/substrate/abi/src/system.rs index bb67c91c..d025e767 100644 --- a/substrate/abi/src/system.rs +++ b/substrate/abi/src/system.rs @@ -3,7 +3,6 @@ use frame_support::dispatch::{DispatchInfo, DispatchError}; use serai_primitives::SeraiAddress; #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Event { ExtrinsicSuccess { dispatch_info: DispatchInfo }, ExtrinsicFailed { dispatch_error: DispatchError, dispatch_info: DispatchInfo }, diff --git a/substrate/abi/src/timestamp.rs b/substrate/abi/src/timestamp.rs index c6e7d8cd..af763928 100644 --- a/substrate/abi/src/timestamp.rs +++ b/substrate/abi/src/timestamp.rs @@ -1,5 +1,9 @@ #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] pub enum Call { - set { now: scale::Compact }, + set { + #[codec(compact)] + now: u64, + }, } diff --git a/substrate/abi/src/tx.rs b/substrate/abi/src/tx.rs new file mode 100644 index 00000000..6c61535b --- /dev/null +++ b/substrate/abi/src/tx.rs @@ -0,0 +1,183 @@ +use scale::Encode; + +use sp_core::sr25519::{Public, Signature}; +use sp_runtime::traits::Verify; + +use serai_primitives::SeraiAddress; + +use frame_support::dispatch::GetDispatchInfo; + +pub trait TransactionMember: + Clone + PartialEq + Eq + core::fmt::Debug + scale::Encode + scale::Decode + scale_info::TypeInfo +{ +} +impl< + T: Clone + + PartialEq + + Eq + + core::fmt::Debug + + scale::Encode + + scale::Decode + + scale_info::TypeInfo, + > TransactionMember for T +{ +} + +type TransactionEncodeAs<'a, Extra> = + (&'a crate::Call, &'a Option<(SeraiAddress, Signature, Extra)>); +type TransactionDecodeAs = (crate::Call, Option<(SeraiAddress, Signature, Extra)>); + +// We use our own Transaction struct, over UncheckedExtrinsic, for more control, a bit more +// simplicity, and in order to be immune to https://github.com/paritytech/polkadot-sdk/issues/2947 +#[allow(private_bounds)] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Transaction< + Call: 'static + TransactionMember + From, + Extra: 'static + TransactionMember, +> { + call: crate::Call, + mapped_call: Call, + signature: Option<(SeraiAddress, Signature, Extra)>, +} + +impl, Extra: 'static + TransactionMember> + Transaction +{ + pub fn new(call: crate::Call, signature: Option<(SeraiAddress, Signature, Extra)>) -> Self { + Self { call: call.clone(), mapped_call: call.into(), signature } + } + + pub fn call(&self) -> &crate::Call { + &self.call + } +} + +impl, Extra: 'static + TransactionMember> + scale::Encode for Transaction +{ + fn using_encoded R>(&self, f: F) -> R { + let tx: TransactionEncodeAs = (&self.call, &self.signature); + tx.using_encoded(f) + } +} +impl, Extra: 'static + TransactionMember> + scale::Decode for Transaction +{ + fn decode(input: &mut I) -> Result { + let (call, signature) = TransactionDecodeAs::decode(input)?; + let mapped_call = Call::from(call.clone()); + Ok(Self { call, mapped_call, signature }) + } +} +impl, Extra: 'static + TransactionMember> + scale_info::TypeInfo for Transaction +{ + type Identity = TransactionDecodeAs; + + // Define the type info as the info of the type equivalent to what we encode as + fn type_info() -> scale_info::Type { + TransactionDecodeAs::::type_info() + } +} + +#[cfg(feature = "serde")] +mod _serde { + use scale::Encode; + use serde::{ser::*, de::*}; + use super::*; + impl, Extra: 'static + TransactionMember> + Serialize for Transaction + { + fn serialize(&self, serializer: S) -> Result { + let encoded = self.encode(); + serializer.serialize_bytes(&encoded) + } + } + #[cfg(feature = "std")] + impl< + 'a, + Call: 'static + TransactionMember + From, + Extra: 'static + TransactionMember, + > Deserialize<'a> for Transaction + { + fn deserialize>(de: D) -> Result { + let bytes = sp_core::bytes::deserialize(de)?; + ::decode(&mut &bytes[..]) + .map_err(|e| serde::de::Error::custom(format!("invalid transaction: {e}"))) + } + } +} + +impl< + Call: 'static + TransactionMember + From + TryInto, + Extra: 'static + TransactionMember, + > sp_runtime::traits::Extrinsic for Transaction +{ + type Call = Call; + type SignaturePayload = (SeraiAddress, Signature, Extra); + fn is_signed(&self) -> Option { + Some(self.signature.is_some()) + } + fn new(call: Call, signature: Option) -> Option { + Some(Self { call: call.clone().try_into().ok()?, mapped_call: call, signature }) + } +} + +impl< + Call: 'static + TransactionMember + From + TryInto, + Extra: 'static + TransactionMember, + > frame_support::traits::ExtrinsicCall for Transaction +{ + fn call(&self) -> &Call { + &self.mapped_call + } +} + +impl< + Call: 'static + TransactionMember + From, + Extra: 'static + TransactionMember + sp_runtime::traits::SignedExtension, + > sp_runtime::traits::ExtrinsicMetadata for Transaction +{ + type SignedExtensions = Extra; + + const VERSION: u8 = 0; +} + +impl< + Call: 'static + TransactionMember + From + GetDispatchInfo, + Extra: 'static + TransactionMember, + > GetDispatchInfo for Transaction +{ + fn get_dispatch_info(&self) -> frame_support::dispatch::DispatchInfo { + self.mapped_call.get_dispatch_info() + } +} + +impl< + Call: 'static + TransactionMember + From, + Extra: 'static + TransactionMember + sp_runtime::traits::SignedExtension, + > sp_runtime::traits::BlindCheckable for Transaction +{ + type Checked = sp_runtime::generic::CheckedExtrinsic; + + fn check( + self, + ) -> Result { + Ok(match self.signature { + Some((signer, signature, extra)) => { + if !signature.verify( + (&self.call, &extra, extra.additional_signed()?).encode().as_slice(), + &signer.into(), + ) { + Err(sp_runtime::transaction_validity::InvalidTransaction::BadProof)? + } + + sp_runtime::generic::CheckedExtrinsic { + signed: Some((signer.into(), extra)), + function: self.mapped_call, + } + } + None => sp_runtime::generic::CheckedExtrinsic { signed: None, function: self.mapped_call }, + }) + } +} diff --git a/substrate/abi/src/validator_sets.rs b/substrate/abi/src/validator_sets.rs index 1630f8ac..1e1e3359 100644 --- a/substrate/abi/src/validator_sets.rs +++ b/substrate/abi/src/validator_sets.rs @@ -1,4 +1,4 @@ -use sp_core::{ConstU32, bounded_vec::BoundedVec}; +use sp_core::{ConstU32, bounded::BoundedVec}; pub use serai_validator_sets_primitives as primitives; @@ -6,11 +6,12 @@ use serai_primitives::*; use serai_validator_sets_primitives::*; #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] pub enum Call { set_keys { network: NetworkId, - removed_participants: Vec, + removed_participants: BoundedVec>, key_pair: KeyPair, signature: Signature, }, @@ -35,7 +36,8 @@ pub enum Call { #[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] #[cfg_attr(feature = "borsh", derive(borsh::BorshSerialize, borsh::BorshDeserialize))] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(all(feature = "std", feature = "serde"), derive(serde::Deserialize))] pub enum Event { NewSet { set: ValidatorSet, diff --git a/substrate/client/src/serai/mod.rs b/substrate/client/src/serai/mod.rs index 1347fc05..52421399 100644 --- a/substrate/client/src/serai/mod.rs +++ b/substrate/client/src/serai/mod.rs @@ -3,7 +3,7 @@ use thiserror::Error; use async_lock::RwLock; use simple_request::{hyper, Request, Client}; -use scale::{Compact, Decode, Encode}; +use scale::{Decode, Encode}; use serde::{Serialize, Deserialize, de::DeserializeOwned}; pub use sp_core::{ @@ -43,8 +43,8 @@ impl Block { /// Returns the time of this block, set by its producer, in milliseconds since the epoch. pub fn time(&self) -> Result { for transaction in &self.transactions { - if let Call::Timestamp(timestamp::Call::set { now }) = &transaction.call { - return Ok(u64::from(*now)); + if let Call::Timestamp(timestamp::Call::set { now }) = transaction.call() { + return Ok(*now); } } Err(SeraiError::InvalidNode("no time was present in block".to_string())) @@ -162,15 +162,14 @@ impl Serai { } fn unsigned(call: Call) -> Transaction { - Transaction { call, signature: None } + Transaction::new(call, None) } pub fn sign(&self, signer: &Pair, call: Call, nonce: u32, tip: u64) -> Transaction { const SPEC_VERSION: u32 = 1; const TX_VERSION: u32 = 1; - let extra = - Extra { era: sp_runtime::generic::Era::Immortal, nonce: Compact(nonce), tip: Compact(tip) }; + let extra = Extra { era: sp_runtime::generic::Era::Immortal, nonce, tip }; let signature_payload = ( &call, &extra, @@ -184,7 +183,7 @@ impl Serai { .encode(); let signature = signer.sign(&signature_payload); - Transaction { call, signature: Some((signer.public().into(), signature, extra)) } + Transaction::new(call, Some((signer.public().into(), signature, extra))) } pub async fn publish(&self, tx: &Transaction) -> Result<(), SeraiError> { diff --git a/substrate/client/src/serai/validator_sets.rs b/substrate/client/src/serai/validator_sets.rs index c4e29644..959f8ee6 100644 --- a/substrate/client/src/serai/validator_sets.rs +++ b/substrate/client/src/serai/validator_sets.rs @@ -180,7 +180,10 @@ impl<'a> SeraiValidatorSets<'a> { pub fn set_keys( network: NetworkId, - removed_participants: Vec, + removed_participants: sp_runtime::BoundedVec< + SeraiAddress, + sp_core::ConstU32<{ primitives::MAX_KEY_SHARES_PER_SET / 3 }>, + >, key_pair: KeyPair, signature: Signature, ) -> Transaction { diff --git a/substrate/client/tests/common/validator_sets.rs b/substrate/client/tests/common/validator_sets.rs index b7257a1c..7924d05c 100644 --- a/substrate/client/tests/common/validator_sets.rs +++ b/substrate/client/tests/common/validator_sets.rs @@ -51,7 +51,12 @@ pub async fn set_keys(serai: &Serai, set: ValidatorSet, key_pair: KeyPair) -> [u // Set the key pair let block = publish_tx( serai, - &SeraiValidatorSets::set_keys(set.network, vec![], key_pair.clone(), Signature(sig.to_bytes())), + &SeraiValidatorSets::set_keys( + set.network, + vec![].try_into().unwrap(), + key_pair.clone(), + Signature(sig.to_bytes()), + ), ) .await; diff --git a/substrate/primitives/src/lib.rs b/substrate/primitives/src/lib.rs index 970cf46e..2af36e22 100644 --- a/substrate/primitives/src/lib.rs +++ b/substrate/primitives/src/lib.rs @@ -37,9 +37,6 @@ pub use balance::*; mod account; pub use account::*; -mod tx; -pub use tx::*; - pub type BlockNumber = u64; pub type Header = sp_runtime::generic::Header; diff --git a/substrate/primitives/src/tx.rs b/substrate/primitives/src/tx.rs deleted file mode 100644 index b97ec3a2..00000000 --- a/substrate/primitives/src/tx.rs +++ /dev/null @@ -1,124 +0,0 @@ -use scale::Encode; - -use sp_core::sr25519::{Public, Signature}; -use sp_runtime::traits::Verify; - -use crate::SeraiAddress; - -trait TransactionMember: - Clone + PartialEq + Eq + core::fmt::Debug + scale::Encode + scale::Decode + scale_info::TypeInfo -{ -} -impl< - T: Clone - + PartialEq - + Eq - + core::fmt::Debug - + scale::Encode - + scale::Decode - + scale_info::TypeInfo, - > TransactionMember for T -{ -} - -// We use our own Transaction struct, over UncheckedExtrinsic, for more control, a bit more -// simplicity, and in order to be immune to https://github.com/paritytech/polkadot-sdk/issues/2947 -#[allow(private_bounds)] -#[derive(Clone, PartialEq, Eq, Debug, scale::Encode, scale::Decode, scale_info::TypeInfo)] -pub struct Transaction { - pub call: Call, - pub signature: Option<(SeraiAddress, Signature, Extra)>, -} - -#[cfg(feature = "serde")] -mod _serde { - use scale::Encode; - use serde::{ser::*, de::*}; - use super::*; - impl Serialize for Transaction { - fn serialize(&self, serializer: S) -> Result { - let encoded = self.encode(); - serializer.serialize_bytes(&encoded) - } - } - #[cfg(feature = "std")] - impl<'a, Call: TransactionMember, Extra: TransactionMember> Deserialize<'a> - for Transaction - { - fn deserialize>(de: D) -> Result { - let bytes = sp_core::bytes::deserialize(de)?; - scale::Decode::decode(&mut &bytes[..]) - .map_err(|e| serde::de::Error::custom(format!("invalid transaction: {e}"))) - } - } -} - -impl sp_runtime::traits::Extrinsic - for Transaction -{ - type Call = Call; - type SignaturePayload = (SeraiAddress, Signature, Extra); - fn is_signed(&self) -> Option { - Some(self.signature.is_some()) - } - fn new(call: Call, signature: Option) -> Option { - Some(Self { call, signature }) - } -} - -impl frame_support::traits::ExtrinsicCall - for Transaction -{ - fn call(&self) -> &Call { - &self.call - } -} - -impl sp_runtime::traits::ExtrinsicMetadata - for Transaction -where - Extra: sp_runtime::traits::SignedExtension, -{ - type SignedExtensions = Extra; - - const VERSION: u8 = 0; -} - -impl frame_support::dispatch::GetDispatchInfo - for Transaction -where - Call: frame_support::dispatch::GetDispatchInfo, -{ - fn get_dispatch_info(&self) -> frame_support::dispatch::DispatchInfo { - self.call.get_dispatch_info() - } -} - -impl sp_runtime::traits::BlindCheckable - for Transaction -where - Extra: sp_runtime::traits::SignedExtension, -{ - type Checked = sp_runtime::generic::CheckedExtrinsic; - - fn check( - self, - ) -> Result { - Ok(match self.signature { - Some((signer, signature, extra)) => { - if !signature.verify( - (&self.call, &extra, extra.additional_signed()?).encode().as_slice(), - &signer.into(), - ) { - Err(sp_runtime::transaction_validity::InvalidTransaction::BadProof)? - } - - sp_runtime::generic::CheckedExtrinsic { - signed: Some((signer.into(), extra)), - function: self.call, - } - } - None => sp_runtime::generic::CheckedExtrinsic { signed: None, function: self.call }, - }) - } -} diff --git a/substrate/runtime/Cargo.toml b/substrate/runtime/Cargo.toml index e4b7d639..54869f6d 100644 --- a/substrate/runtime/Cargo.toml +++ b/substrate/runtime/Cargo.toml @@ -49,6 +49,7 @@ 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 } serai-primitives = { path = "../primitives", default-features = false } +serai-abi = { path = "../abi", default-features = false, features = ["serde"] } pallet-timestamp = { git = "https://github.com/serai-dex/substrate", default-features = false } pallet-authorship = { git = "https://github.com/serai-dex/substrate", default-features = false } @@ -102,6 +103,8 @@ std = [ "frame-executive/std", "serai-primitives/std", + "serai-abi/std", + "serai-abi/serde", "pallet-timestamp/std", "pallet-authorship/std", diff --git a/substrate/runtime/src/abi.rs b/substrate/runtime/src/abi.rs new file mode 100644 index 00000000..45c2aa33 --- /dev/null +++ b/substrate/runtime/src/abi.rs @@ -0,0 +1,363 @@ +use core::marker::PhantomData; + +use scale::{Encode, Decode}; + +use serai_abi::Call; + +use crate::{ + Vec, + primitives::{PublicKey, SeraiAddress}, + timestamp, coins, dex, + validator_sets::{self, MembershipProof}, + in_instructions, signals, babe, grandpa, RuntimeCall, +}; + +impl From for RuntimeCall { + fn from(call: Call) -> RuntimeCall { + match call { + Call::Timestamp(serai_abi::timestamp::Call::set { now }) => { + RuntimeCall::Timestamp(timestamp::Call::set { now }) + } + Call::Coins(coins) => match coins { + serai_abi::coins::Call::transfer { to, balance } => { + RuntimeCall::Coins(coins::Call::transfer { to: to.into(), balance }) + } + serai_abi::coins::Call::burn { balance } => { + RuntimeCall::Coins(coins::Call::burn { balance }) + } + serai_abi::coins::Call::burn_with_instruction { instruction } => { + RuntimeCall::Coins(coins::Call::burn_with_instruction { instruction }) + } + }, + Call::LiquidityTokens(lt) => match lt { + serai_abi::coins::LiquidityTokensCall::transfer { to, balance } => { + RuntimeCall::LiquidityTokens(coins::Call::transfer { to: to.into(), balance }) + } + serai_abi::coins::LiquidityTokensCall::burn { balance } => { + RuntimeCall::LiquidityTokens(coins::Call::burn { balance }) + } + }, + Call::Dex(dex) => match dex { + serai_abi::dex::Call::add_liquidity { + coin, + coin_desired, + sri_desired, + coin_min, + sri_min, + mint_to, + } => RuntimeCall::Dex(dex::Call::add_liquidity { + coin, + coin_desired, + sri_desired, + coin_min, + sri_min, + mint_to: mint_to.into(), + }), + serai_abi::dex::Call::remove_liquidity { + coin, + lp_token_burn, + coin_min_receive, + sri_min_receive, + withdraw_to, + } => RuntimeCall::Dex(dex::Call::remove_liquidity { + coin, + lp_token_burn, + coin_min_receive, + sri_min_receive, + withdraw_to: withdraw_to.into(), + }), + serai_abi::dex::Call::swap_exact_tokens_for_tokens { + path, + amount_in, + amount_out_min, + send_to, + } => RuntimeCall::Dex(dex::Call::swap_exact_tokens_for_tokens { + path, + amount_in, + amount_out_min, + send_to: send_to.into(), + }), + serai_abi::dex::Call::swap_tokens_for_exact_tokens { + path, + amount_out, + amount_in_max, + send_to, + } => RuntimeCall::Dex(dex::Call::swap_tokens_for_exact_tokens { + path, + amount_out, + amount_in_max, + send_to: send_to.into(), + }), + }, + Call::ValidatorSets(vs) => match vs { + serai_abi::validator_sets::Call::set_keys { + network, + removed_participants, + key_pair, + signature, + } => RuntimeCall::ValidatorSets(validator_sets::Call::set_keys { + network, + removed_participants: <_>::try_from( + removed_participants.into_iter().map(PublicKey::from).collect::>(), + ) + .unwrap(), + key_pair, + signature, + }), + serai_abi::validator_sets::Call::report_slashes { network, slashes, signature } => { + RuntimeCall::ValidatorSets(validator_sets::Call::report_slashes { + network, + slashes: <_>::try_from( + slashes + .into_iter() + .map(|(addr, slash)| (PublicKey::from(addr), slash)) + .collect::>(), + ) + .unwrap(), + signature, + }) + } + serai_abi::validator_sets::Call::allocate { network, amount } => { + RuntimeCall::ValidatorSets(validator_sets::Call::allocate { network, amount }) + } + serai_abi::validator_sets::Call::deallocate { network, amount } => { + RuntimeCall::ValidatorSets(validator_sets::Call::deallocate { network, amount }) + } + serai_abi::validator_sets::Call::claim_deallocation { network, session } => { + RuntimeCall::ValidatorSets(validator_sets::Call::claim_deallocation { network, session }) + } + }, + Call::InInstructions(ii) => match ii { + serai_abi::in_instructions::Call::execute_batch { batch } => { + RuntimeCall::InInstructions(in_instructions::Call::execute_batch { batch }) + } + }, + Call::Signals(signals) => match signals { + serai_abi::signals::Call::register_retirement_signal { in_favor_of } => { + RuntimeCall::Signals(signals::Call::register_retirement_signal { in_favor_of }) + } + serai_abi::signals::Call::revoke_retirement_signal { retirement_signal_id } => { + RuntimeCall::Signals(signals::Call::revoke_retirement_signal { retirement_signal_id }) + } + serai_abi::signals::Call::favor { signal_id, for_network } => { + RuntimeCall::Signals(signals::Call::favor { signal_id, for_network }) + } + serai_abi::signals::Call::revoke_favor { signal_id, for_network } => { + RuntimeCall::Signals(signals::Call::revoke_favor { signal_id, for_network }) + } + serai_abi::signals::Call::stand_against { signal_id, for_network } => { + RuntimeCall::Signals(signals::Call::stand_against { signal_id, for_network }) + } + }, + Call::Babe(babe) => match babe { + serai_abi::babe::Call::report_equivocation(report) => { + RuntimeCall::Babe(babe::Call::report_equivocation { + // TODO: Find a better way to go from Proof<[u8; 32]> to Proof + equivocation_proof: <_>::decode(&mut report.equivocation_proof.encode().as_slice()) + .unwrap(), + key_owner_proof: MembershipProof(report.key_owner_proof.into(), PhantomData), + }) + } + serai_abi::babe::Call::report_equivocation_unsigned(report) => { + RuntimeCall::Babe(babe::Call::report_equivocation_unsigned { + // TODO: Find a better way to go from Proof<[u8; 32]> to Proof + equivocation_proof: <_>::decode(&mut report.equivocation_proof.encode().as_slice()) + .unwrap(), + key_owner_proof: MembershipProof(report.key_owner_proof.into(), PhantomData), + }) + } + }, + Call::Grandpa(grandpa) => match grandpa { + serai_abi::grandpa::Call::report_equivocation(report) => { + RuntimeCall::Grandpa(grandpa::Call::report_equivocation { + // TODO: Find a better way to go from Proof<[u8; 32]> to Proof + equivocation_proof: <_>::decode(&mut report.equivocation_proof.encode().as_slice()) + .unwrap(), + key_owner_proof: MembershipProof(report.key_owner_proof.into(), PhantomData), + }) + } + serai_abi::grandpa::Call::report_equivocation_unsigned(report) => { + RuntimeCall::Grandpa(grandpa::Call::report_equivocation_unsigned { + // TODO: Find a better way to go from Proof<[u8; 32]> to Proof + equivocation_proof: <_>::decode(&mut report.equivocation_proof.encode().as_slice()) + .unwrap(), + key_owner_proof: MembershipProof(report.key_owner_proof.into(), PhantomData), + }) + } + }, + } + } +} + +impl TryInto for RuntimeCall { + type Error = (); + + fn try_into(self) -> Result { + Ok(match self { + RuntimeCall::Timestamp(timestamp::Call::set { now }) => { + Call::Timestamp(serai_abi::timestamp::Call::set { now }) + } + RuntimeCall::Coins(call) => Call::Coins(match call { + coins::Call::transfer { to, balance } => { + serai_abi::coins::Call::transfer { to: to.into(), balance } + } + coins::Call::burn { balance } => serai_abi::coins::Call::burn { balance }, + coins::Call::burn_with_instruction { instruction } => { + serai_abi::coins::Call::burn_with_instruction { instruction } + } + _ => Err(())?, + }), + RuntimeCall::LiquidityTokens(call) => Call::LiquidityTokens(match call { + coins::Call::transfer { to, balance } => { + serai_abi::coins::LiquidityTokensCall::transfer { to: to.into(), balance } + } + coins::Call::burn { balance } => serai_abi::coins::LiquidityTokensCall::burn { balance }, + _ => Err(())?, + }), + RuntimeCall::Dex(call) => Call::Dex(match call { + dex::Call::add_liquidity { + coin, + coin_desired, + sri_desired, + coin_min, + sri_min, + mint_to, + } => serai_abi::dex::Call::add_liquidity { + coin, + coin_desired, + sri_desired, + coin_min, + sri_min, + mint_to: mint_to.into(), + }, + dex::Call::remove_liquidity { + coin, + lp_token_burn, + coin_min_receive, + sri_min_receive, + withdraw_to, + } => serai_abi::dex::Call::remove_liquidity { + coin, + lp_token_burn, + coin_min_receive, + sri_min_receive, + withdraw_to: withdraw_to.into(), + }, + dex::Call::swap_exact_tokens_for_tokens { path, amount_in, amount_out_min, send_to } => { + serai_abi::dex::Call::swap_exact_tokens_for_tokens { + path, + amount_in, + amount_out_min, + send_to: send_to.into(), + } + } + dex::Call::swap_tokens_for_exact_tokens { path, amount_out, amount_in_max, send_to } => { + serai_abi::dex::Call::swap_tokens_for_exact_tokens { + path, + amount_out, + amount_in_max, + send_to: send_to.into(), + } + } + _ => Err(())?, + }), + RuntimeCall::ValidatorSets(call) => Call::ValidatorSets(match call { + validator_sets::Call::set_keys { network, removed_participants, key_pair, signature } => { + serai_abi::validator_sets::Call::set_keys { + network, + removed_participants: <_>::try_from( + removed_participants.into_iter().map(SeraiAddress::from).collect::>(), + ) + .unwrap(), + key_pair, + signature, + } + } + validator_sets::Call::report_slashes { network, slashes, signature } => { + serai_abi::validator_sets::Call::report_slashes { + network, + slashes: <_>::try_from( + slashes + .into_iter() + .map(|(addr, slash)| (SeraiAddress::from(addr), slash)) + .collect::>(), + ) + .unwrap(), + signature, + } + } + validator_sets::Call::allocate { network, amount } => { + serai_abi::validator_sets::Call::allocate { network, amount } + } + validator_sets::Call::deallocate { network, amount } => { + serai_abi::validator_sets::Call::deallocate { network, amount } + } + validator_sets::Call::claim_deallocation { network, session } => { + serai_abi::validator_sets::Call::claim_deallocation { network, session } + } + _ => Err(())?, + }), + RuntimeCall::InInstructions(call) => Call::InInstructions(match call { + in_instructions::Call::execute_batch { batch } => { + serai_abi::in_instructions::Call::execute_batch { batch } + } + _ => Err(())?, + }), + RuntimeCall::Signals(call) => Call::Signals(match call { + signals::Call::register_retirement_signal { in_favor_of } => { + serai_abi::signals::Call::register_retirement_signal { in_favor_of } + } + signals::Call::revoke_retirement_signal { retirement_signal_id } => { + serai_abi::signals::Call::revoke_retirement_signal { retirement_signal_id } + } + signals::Call::favor { signal_id, for_network } => { + serai_abi::signals::Call::favor { signal_id, for_network } + } + signals::Call::revoke_favor { signal_id, for_network } => { + serai_abi::signals::Call::revoke_favor { signal_id, for_network } + } + signals::Call::stand_against { signal_id, for_network } => { + serai_abi::signals::Call::stand_against { signal_id, for_network } + } + _ => Err(())?, + }), + RuntimeCall::Babe(call) => Call::Babe(match call { + babe::Call::report_equivocation { equivocation_proof, key_owner_proof } => { + serai_abi::babe::Call::report_equivocation(serai_abi::babe::ReportEquivocation { + // TODO: Find a better way to go from Proof to Proof<[u8; 32]> + equivocation_proof: <_>::decode(&mut equivocation_proof.encode().as_slice()).unwrap(), + key_owner_proof: key_owner_proof.0.into(), + }) + } + babe::Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } => { + serai_abi::babe::Call::report_equivocation_unsigned(serai_abi::babe::ReportEquivocation { + // TODO: Find a better way to go from Proof to Proof<[u8; 32]> + equivocation_proof: <_>::decode(&mut equivocation_proof.encode().as_slice()).unwrap(), + key_owner_proof: key_owner_proof.0.into(), + }) + } + _ => Err(())?, + }), + RuntimeCall::Grandpa(call) => Call::Grandpa(match call { + grandpa::Call::report_equivocation { equivocation_proof, key_owner_proof } => { + serai_abi::grandpa::Call::report_equivocation(serai_abi::grandpa::ReportEquivocation { + // TODO: Find a better way to go from Proof to Proof<[u8; 32]> + equivocation_proof: <_>::decode(&mut equivocation_proof.encode().as_slice()).unwrap(), + key_owner_proof: key_owner_proof.0.into(), + }) + } + grandpa::Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } => { + serai_abi::grandpa::Call::report_equivocation_unsigned( + serai_abi::grandpa::ReportEquivocation { + // TODO: Find a better way to go from Proof to Proof<[u8; 32]> + equivocation_proof: <_>::decode(&mut equivocation_proof.encode().as_slice()).unwrap(), + key_owner_proof: key_owner_proof.0.into(), + }, + ) + } + _ => Err(())?, + }), + _ => Err(())?, + }) + } +} diff --git a/substrate/runtime/src/lib.rs b/substrate/runtime/src/lib.rs index 9a534a72..bef2c062 100644 --- a/substrate/runtime/src/lib.rs +++ b/substrate/runtime/src/lib.rs @@ -64,6 +64,8 @@ use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use babe::AuthorityId as BabeId; use grandpa::AuthorityId as GrandpaId; +mod abi; + /// Nonce of a transaction in the chain, for a given account. pub type Nonce = u32; @@ -81,7 +83,7 @@ pub type SignedExtra = ( transaction_payment::ChargeTransactionPayment, ); -pub type Transaction = serai_primitives::Transaction; +pub type Transaction = serai_abi::tx::Transaction; pub type Block = generic::Block; pub type BlockId = generic::BlockId; @@ -161,35 +163,9 @@ parameter_types! { pub struct CallFilter; impl Contains for CallFilter { fn contains(call: &RuntimeCall) -> bool { - match call { - RuntimeCall::Timestamp(call) => match call { - timestamp::Call::set { .. } => true, - timestamp::Call::__Ignore(_, _) => false, - }, - - // 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::LiquidityTokens(call) => match call { - coins::Call::transfer { .. } | coins::Call::burn { .. } => true, - coins::Call::burn_with_instruction { .. } | coins::Call::__Ignore(_, _) => false, - }, - RuntimeCall::Dex(call) => !matches!(call, dex::Call::__Ignore(_, _)), - RuntimeCall::ValidatorSets(call) => !matches!(call, validator_sets::Call::__Ignore(_, _)), - RuntimeCall::InInstructions(call) => !matches!(call, in_instructions::Call::__Ignore(_, _)), - RuntimeCall::Signals(call) => !matches!(call, signals::Call::__Ignore(_, _)), - - RuntimeCall::Babe(call) => match call { - babe::Call::report_equivocation { .. } | - babe::Call::report_equivocation_unsigned { .. } => true, - babe::Call::plan_config_change { .. } | babe::Call::__Ignore(_, _) => false, - }, - - RuntimeCall::Grandpa(call) => match call { - grandpa::Call::report_equivocation { .. } | - grandpa::Call::report_equivocation_unsigned { .. } => true, - grandpa::Call::note_stalled { .. } | grandpa::Call::__Ignore(_, _) => false, - }, - } + // If the call is defined in our ABI, it's allowed + let call: Result = call.clone().try_into(); + call.is_ok() } } diff --git a/substrate/validator-sets/pallet/src/lib.rs b/substrate/validator-sets/pallet/src/lib.rs index c852c4ce..6ea89764 100644 --- a/substrate/validator-sets/pallet/src/lib.rs +++ b/substrate/validator-sets/pallet/src/lib.rs @@ -873,7 +873,7 @@ pub mod pallet { pub fn set_keys( origin: OriginFor, network: NetworkId, - removed_participants: Vec, + removed_participants: BoundedVec>, key_pair: KeyPair, signature: Signature, ) -> DispatchResult {