mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-11 13:39:25 +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"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"borsh",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"parity-scale-codec",
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
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.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
|
||||
pub enum Call {
|
||||
|
||||
@@ -21,6 +21,7 @@ workspace = true
|
||||
|
||||
[dependencies]
|
||||
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-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]
|
||||
std = [
|
||||
"scale/std",
|
||||
"borsh/std",
|
||||
|
||||
"sp-core/std",
|
||||
"sp-application-crypto/std",
|
||||
|
||||
@@ -7,10 +7,11 @@ extern crate alloc;
|
||||
#[expect(clippy::cast_possible_truncation)]
|
||||
#[frame_support::pallet]
|
||||
mod pallet {
|
||||
use sp_core::sr25519::Public;
|
||||
use sp_application_crypto::RuntimePublic;
|
||||
|
||||
use frame_support::{pallet_prelude::*, dispatch::RawOrigin};
|
||||
use frame_system::pallet_prelude::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
|
||||
use serai_abi::{primitives::prelude::*, in_instructions::Event};
|
||||
|
||||
@@ -59,6 +60,127 @@ mod pallet {
|
||||
fn emit_event(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]
|
||||
@@ -67,7 +189,29 @@ mod pallet {
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight((0, DispatchClass::Normal))] // TODO
|
||||
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
|
||||
let current_block_number = frame_system::Pallet::<T>::block_number();
|
||||
if BlockOfLastBatch::<T>::get(network) == Some(current_block_number) {
|
||||
|
||||
@@ -10,15 +10,6 @@ use crate::{
|
||||
mod 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.
|
||||
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, BorshSerialize, BorshDeserialize)]
|
||||
pub enum InInstruction {
|
||||
@@ -33,10 +24,10 @@ pub enum InInstruction {
|
||||
},
|
||||
/// Transfer the coins to a Serai address, swapping some for SRI.
|
||||
TransferWithSwap {
|
||||
/// The Serai address to transfer the coins to, after swapping some.
|
||||
/// The Serai address to transfer the coins to.
|
||||
to: SeraiAddress,
|
||||
/// 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.
|
||||
sri: Amount,
|
||||
},
|
||||
@@ -47,23 +38,28 @@ pub enum InInstruction {
|
||||
},
|
||||
/// Swap part of the coins to SRI and add the coins as liquidity.
|
||||
SwapAndAddLiquidity {
|
||||
/// The owner to-be of the added liquidity.
|
||||
owner: SeraiAddress,
|
||||
/// The amount of SRI to add within the liquidity position.
|
||||
sri: Amount,
|
||||
/// The minimum amount of the coin to add as liquidity.
|
||||
minimum_coin: Amount,
|
||||
/// The recipient to-be of the added liquidity.
|
||||
address: SeraiAddress,
|
||||
/// The amount of the coin to add within the liquidity position.
|
||||
coin: Amount,
|
||||
/// The minimum amount of SRI to add as liquidity.
|
||||
sri_minimum: Amount,
|
||||
/// The amount of SRI to swap to and send to the owner to-be to pay for transactions on Serai.
|
||||
sri_for_fees: Amount,
|
||||
},
|
||||
/// Swap the coins.
|
||||
Swap {
|
||||
/// The minimum balance to receive.
|
||||
minimum_out: Balance,
|
||||
/// The destination to transfer the balance to.
|
||||
///
|
||||
/// If `Destination::Burn`, the balance out will be burnt with the included `OutInstruction`.
|
||||
destination: Destination,
|
||||
/// The destination to received the coins swapped to with.
|
||||
address: SeraiAddress,
|
||||
/// The minimum balance to receive from the swap.
|
||||
minimum_to_receive: Balance,
|
||||
},
|
||||
/// 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