mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 21:49:26 +00:00
Dispatch InInstruction as expected
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -8433,6 +8433,7 @@ name = "serai-in-instructions-pallet"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitvec",
|
"bitvec",
|
||||||
|
"borsh",
|
||||||
"frame-support",
|
"frame-support",
|
||||||
"frame-system",
|
"frame-system",
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
use borsh::{BorshSerialize, BorshDeserialize};
|
use borsh::{BorshSerialize, BorshDeserialize};
|
||||||
|
|
||||||
use serai_primitives::{
|
use serai_primitives::{
|
||||||
BlockHash, network_id::ExternalNetworkId, validator_sets::Session, instructions::SignedBatch,
|
BlockHash, network_id::ExternalNetworkId, validator_sets::Session, address::SeraiAddress,
|
||||||
|
instructions::SignedBatch,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The address used for executing `InInstruction`s.
|
||||||
|
pub fn address() -> SeraiAddress {
|
||||||
|
SeraiAddress::system(borsh::to_vec(b"InInstructions").unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
/// A call to `InInstruction`s.
|
/// A call to `InInstruction`s.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum Call {
|
pub enum Call {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
|
||||||
|
borsh = { version = "1", default-features = false }
|
||||||
|
|
||||||
sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
|
sp-core = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
|
||||||
sp-application-crypto = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
|
sp-application-crypto = { git = "https://github.com/serai-dex/patch-polkadot-sdk", default-features = false }
|
||||||
@@ -40,6 +41,7 @@ serai-genesis-liquidity-pallet = { path = "../genesis-liquidity", default-featur
|
|||||||
[features]
|
[features]
|
||||||
std = [
|
std = [
|
||||||
"scale/std",
|
"scale/std",
|
||||||
|
"borsh/std",
|
||||||
|
|
||||||
"sp-core/std",
|
"sp-core/std",
|
||||||
"sp-application-crypto/std",
|
"sp-application-crypto/std",
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ extern crate alloc;
|
|||||||
#[expect(clippy::cast_possible_truncation)]
|
#[expect(clippy::cast_possible_truncation)]
|
||||||
#[frame_support::pallet]
|
#[frame_support::pallet]
|
||||||
mod pallet {
|
mod pallet {
|
||||||
|
use sp_core::sr25519::Public;
|
||||||
use sp_application_crypto::RuntimePublic;
|
use sp_application_crypto::RuntimePublic;
|
||||||
|
|
||||||
|
use frame_support::{pallet_prelude::*, dispatch::RawOrigin};
|
||||||
use frame_system::pallet_prelude::*;
|
use frame_system::pallet_prelude::*;
|
||||||
use frame_support::pallet_prelude::*;
|
|
||||||
|
|
||||||
use serai_abi::{primitives::prelude::*, in_instructions::Event};
|
use serai_abi::{primitives::prelude::*, in_instructions::Event};
|
||||||
|
|
||||||
@@ -59,6 +60,127 @@ mod pallet {
|
|||||||
fn emit_event(event: Event) {
|
fn emit_event(event: Event) {
|
||||||
Core::<T>::emit_event(event)
|
Core::<T>::emit_event(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Execute an `InInstructionWithBalance`.
|
||||||
|
///
|
||||||
|
/// We execute this within a database layer to ensure it's atomic, executing entirely or not at
|
||||||
|
/// all.
|
||||||
|
#[frame_support::transactional]
|
||||||
|
fn execute(instruction: InInstructionWithBalance) -> DispatchResult {
|
||||||
|
let InInstructionWithBalance { instruction, balance: external_balance } = instruction;
|
||||||
|
let balance = <Balance as From<ExternalBalance>>::from(external_balance);
|
||||||
|
|
||||||
|
// Mint the balance to ourself
|
||||||
|
let address = serai_abi::in_instructions::address();
|
||||||
|
Coins::<T>::mint(address.into(), balance)?;
|
||||||
|
|
||||||
|
match instruction {
|
||||||
|
InInstruction::GenesisLiquidity(address) => {
|
||||||
|
serai_genesis_liquidity_pallet::Pallet::<T>::add_liquidity(address, external_balance)?;
|
||||||
|
}
|
||||||
|
InInstruction::SwapToStakedSri { validator, minimum } => todo!("TODO"),
|
||||||
|
InInstruction::TransferWithSwap { to, maximum_to_swap, sri } => {
|
||||||
|
serai_dex_pallet::Pallet::<T>::swap_for(
|
||||||
|
RawOrigin::Signed(address.into()).into(),
|
||||||
|
Balance { coin: Coin::Serai, amount: sri },
|
||||||
|
Balance { coin: balance.coin, amount: maximum_to_swap },
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Coins::<T>::transfer_fn(
|
||||||
|
address.into(),
|
||||||
|
to.into(),
|
||||||
|
Balance {
|
||||||
|
coin: balance.coin,
|
||||||
|
amount: Coins::<T>::balance(Public::from(address), balance.coin),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
Coins::<T>::transfer_fn(
|
||||||
|
address.into(),
|
||||||
|
to.into(),
|
||||||
|
Balance {
|
||||||
|
coin: Coin::Serai,
|
||||||
|
amount: Coins::<T>::balance(Public::from(address), Coin::Serai),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
InInstruction::Transfer { to } => {
|
||||||
|
Coins::<T>::transfer_fn(address.into(), to.into(), balance)?;
|
||||||
|
}
|
||||||
|
InInstruction::SwapAndAddLiquidity {
|
||||||
|
address: destination,
|
||||||
|
coin,
|
||||||
|
sri_minimum,
|
||||||
|
sri_for_fees,
|
||||||
|
} => {
|
||||||
|
let external_coin = external_balance.coin;
|
||||||
|
serai_dex_pallet::Pallet::<T>::swap(
|
||||||
|
RawOrigin::Signed(address.into()).into(),
|
||||||
|
Balance {
|
||||||
|
coin: Coin::External(external_coin),
|
||||||
|
amount: (balance.amount - coin).ok_or(serai_dex_pallet::Error::<T>::Underflow)?,
|
||||||
|
},
|
||||||
|
Balance {
|
||||||
|
coin: Coin::Serai,
|
||||||
|
amount: (sri_minimum + sri_for_fees).ok_or(serai_dex_pallet::Error::<T>::Overflow)?,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let sri_intended = (Coins::<T>::balance(Public::from(address), Coin::Serai) -
|
||||||
|
sri_for_fees)
|
||||||
|
.expect("swapped to amount sufficient for minimum, fees, but received less than fees?");
|
||||||
|
let coin_intended = coin;
|
||||||
|
let coin_minimum = coin;
|
||||||
|
serai_dex_pallet::Pallet::<T>::add_liquidity(
|
||||||
|
RawOrigin::Signed(address.into()).into(),
|
||||||
|
external_coin,
|
||||||
|
sri_intended,
|
||||||
|
coin_intended,
|
||||||
|
sri_minimum,
|
||||||
|
coin_minimum,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Transfer the rest, which will be more than the amount requested for fees, to the
|
||||||
|
// destination
|
||||||
|
Coins::<T>::transfer_fn(
|
||||||
|
address.into(),
|
||||||
|
destination.into(),
|
||||||
|
Balance {
|
||||||
|
coin: Coin::Serai,
|
||||||
|
amount: Coins::<T>::balance(Public::from(address), Coin::Serai),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
InInstruction::Swap { address: destination, minimum_to_receive } => {
|
||||||
|
serai_dex_pallet::Pallet::<T>::swap(
|
||||||
|
RawOrigin::Signed(address.into()).into(),
|
||||||
|
balance,
|
||||||
|
minimum_to_receive,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let coin = minimum_to_receive.coin;
|
||||||
|
let received_amount = Coins::<T>::balance(Public::from(address), coin);
|
||||||
|
let received = Balance { coin, amount: received_amount };
|
||||||
|
Coins::<T>::transfer_fn(address.into(), destination.into(), received)?;
|
||||||
|
}
|
||||||
|
InInstruction::SwapOut { instruction, minimum_to_receive } => {
|
||||||
|
serai_dex_pallet::Pallet::<T>::swap(
|
||||||
|
RawOrigin::Signed(address.into()).into(),
|
||||||
|
balance,
|
||||||
|
minimum_to_receive.into(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let coin = minimum_to_receive.coin;
|
||||||
|
let received_amount = Coins::<T>::balance(Public::from(address), Coin::from(coin));
|
||||||
|
let received = ExternalBalance { coin, amount: received_amount };
|
||||||
|
Coins::<T>::burn_with_instruction(
|
||||||
|
RawOrigin::Signed(address.into()).into(),
|
||||||
|
OutInstructionWithBalance { instruction, balance: received },
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pallet::call]
|
#[pallet::call]
|
||||||
@@ -67,7 +189,29 @@ mod pallet {
|
|||||||
#[pallet::call_index(0)]
|
#[pallet::call_index(0)]
|
||||||
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
||||||
pub fn execute_batch(origin: OriginFor<T>, batch: SignedBatch) -> DispatchResult {
|
pub fn execute_batch(origin: OriginFor<T>, batch: SignedBatch) -> DispatchResult {
|
||||||
todo!("TODO")
|
let batch = batch.batch;
|
||||||
|
let network = batch.network();
|
||||||
|
|
||||||
|
let mut in_instruction_results = bitvec::vec::BitVec::new();
|
||||||
|
for instruction in batch.instructions() {
|
||||||
|
in_instruction_results.push(Self::execute(instruction.clone()).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
// The publishing session is always the current session
|
||||||
|
let publishing_session =
|
||||||
|
serai_validator_sets_pallet::Pallet::<T>::current_session(NetworkId::from(network))
|
||||||
|
.expect("`SignedBatch` for a network without a session was validated");
|
||||||
|
|
||||||
|
Self::emit_event(Event::Batch {
|
||||||
|
network,
|
||||||
|
publishing_session,
|
||||||
|
id: batch.id(),
|
||||||
|
external_network_block_hash: batch.external_network_block_hash(),
|
||||||
|
in_instructions_hash: sp_core::blake2_256(&borsh::to_vec(batch.instructions()).unwrap()),
|
||||||
|
in_instruction_results,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +265,13 @@ mod pallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify every coin with the `Batch` corresponds to this network
|
||||||
|
for instruction in batch.batch.instructions() {
|
||||||
|
if instruction.balance.coin.network() != network {
|
||||||
|
Err(InvalidTransaction::Custom(2))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verify this is the first `Batch` for this block
|
// Verify this is the first `Batch` for this block
|
||||||
let current_block_number = frame_system::Pallet::<T>::block_number();
|
let current_block_number = frame_system::Pallet::<T>::block_number();
|
||||||
if BlockOfLastBatch::<T>::get(network) == Some(current_block_number) {
|
if BlockOfLastBatch::<T>::get(network) == Some(current_block_number) {
|
||||||
|
|||||||
@@ -10,15 +10,6 @@ use crate::{
|
|||||||
mod batch;
|
mod batch;
|
||||||
pub use batch::*;
|
pub use batch::*;
|
||||||
|
|
||||||
/// The destination for coins.
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
|
||||||
pub enum Destination {
|
|
||||||
/// The Serai address to transfer the coins to.
|
|
||||||
Serai(SeraiAddress),
|
|
||||||
/// Burn the coins with the included `OutInstruction`.
|
|
||||||
Burn(OutInstruction),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An instruction on how to handle coins in.
|
/// An instruction on how to handle coins in.
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
||||||
pub enum InInstruction {
|
pub enum InInstruction {
|
||||||
@@ -33,10 +24,10 @@ pub enum InInstruction {
|
|||||||
},
|
},
|
||||||
/// Transfer the coins to a Serai address, swapping some for SRI.
|
/// Transfer the coins to a Serai address, swapping some for SRI.
|
||||||
TransferWithSwap {
|
TransferWithSwap {
|
||||||
/// The Serai address to transfer the coins to, after swapping some.
|
/// The Serai address to transfer the coins to.
|
||||||
to: SeraiAddress,
|
to: SeraiAddress,
|
||||||
/// The maximum amount of coins to swap for the intended amount of SRI.
|
/// The maximum amount of coins to swap for the intended amount of SRI.
|
||||||
maximum_swap: Amount,
|
maximum_to_swap: Amount,
|
||||||
/// The SRI amount to swap some of the coins for.
|
/// The SRI amount to swap some of the coins for.
|
||||||
sri: Amount,
|
sri: Amount,
|
||||||
},
|
},
|
||||||
@@ -47,23 +38,28 @@ pub enum InInstruction {
|
|||||||
},
|
},
|
||||||
/// Swap part of the coins to SRI and add the coins as liquidity.
|
/// Swap part of the coins to SRI and add the coins as liquidity.
|
||||||
SwapAndAddLiquidity {
|
SwapAndAddLiquidity {
|
||||||
/// The owner to-be of the added liquidity.
|
/// The recipient to-be of the added liquidity.
|
||||||
owner: SeraiAddress,
|
address: SeraiAddress,
|
||||||
/// The amount of SRI to add within the liquidity position.
|
/// The amount of the coin to add within the liquidity position.
|
||||||
sri: Amount,
|
coin: Amount,
|
||||||
/// The minimum amount of the coin to add as liquidity.
|
/// The minimum amount of SRI to add as liquidity.
|
||||||
minimum_coin: Amount,
|
sri_minimum: Amount,
|
||||||
/// The amount of SRI to swap to and send to the owner to-be to pay for transactions on Serai.
|
/// The amount of SRI to swap to and send to the owner to-be to pay for transactions on Serai.
|
||||||
sri_for_fees: Amount,
|
sri_for_fees: Amount,
|
||||||
},
|
},
|
||||||
/// Swap the coins.
|
/// Swap the coins.
|
||||||
Swap {
|
Swap {
|
||||||
/// The minimum balance to receive.
|
/// The destination to received the coins swapped to with.
|
||||||
minimum_out: Balance,
|
address: SeraiAddress,
|
||||||
/// The destination to transfer the balance to.
|
/// The minimum balance to receive from the swap.
|
||||||
///
|
minimum_to_receive: Balance,
|
||||||
/// If `Destination::Burn`, the balance out will be burnt with the included `OutInstruction`.
|
},
|
||||||
destination: Destination,
|
/// Swap the coins, burning them with the included `OutInstruction`.
|
||||||
|
SwapOut {
|
||||||
|
/// The instruction to burn the coins swapped to with.
|
||||||
|
instruction: OutInstruction,
|
||||||
|
/// The minimum balance to receive from the swap.
|
||||||
|
minimum_to_receive: ExternalBalance,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user