Skip to content

Commit

Permalink
bug fixes & optimizations
Browse files Browse the repository at this point in the history
  • Loading branch information
akildemir committed Sep 21, 2023
1 parent 0802ceb commit 187ef15
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 82 deletions.
4 changes: 2 additions & 2 deletions substrate/client/src/serai/dex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ impl Serai {
amount_out_min: Amount,
address: SeraiAddress,
) -> Payload<Composite<()>> {
let path = if to_coin == Coin::Serai {
let path = if to_coin.is_native() {
BoundedVec::truncate_from(vec![from_coin, Coin::Serai])
} else if from_coin == Coin::Serai {
} else if from_coin.is_native() {
BoundedVec::truncate_from(vec![Coin::Serai, to_coin])
} else {
BoundedVec::truncate_from(vec![from_coin, Coin::Serai, to_coin])
Expand Down
171 changes: 133 additions & 38 deletions substrate/client/tests/dex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ use serai_runtime::in_instructions::primitives::DexCall;
use serai_client::{
primitives::{
Amount, NetworkId, Coin, Balance, BlockHash, insecure_pair_from_name, ExternalAddress,
SeraiAddress,
},
in_instructions::primitives::{
InInstruction, InInstructionWithBalance, Batch, IN_INSTRUCTION_EXECUTOR,
InInstruction, InInstructionWithBalance, Batch, IN_INSTRUCTION_EXECUTOR, OutAddress,
},
dex::DexEvent,
Serai,
Expand Down Expand Up @@ -90,7 +91,8 @@ serai_test!(
let coin2 = Coin::Ether;
let pair = insecure_pair_from_name("Ferdie");
let serai = serai().await;
let mut batch_id = 0;
let mut coin1_batch_id = 0;
let mut coin2_batch_id = 0;

// create pools
common_create_pool(coin1, 0, pair.clone()).await;
Expand All @@ -100,61 +102,154 @@ serai_test!(
mint_coin(
Balance { coin: coin1, amount: Amount(100_000000000000) },
NetworkId::Monero,
batch_id,
coin1_batch_id,
pair.clone().public().into(),
)
.await;
batch_id += 1;
coin1_batch_id += 1;
mint_coin(
Balance { coin: coin2, amount: Amount(100_000000000000) },
NetworkId::Ethereum,
0,
coin2_batch_id,
pair.clone().public().into(),
)
.await;
coin2_batch_id += 1;

// add liquidity to pools
common_add_liquidity(coin1, Amount(50_000000000000), Amount(50_000000000000), 2, pair.clone())
.await;
common_add_liquidity(coin2, Amount(50_000000000000), Amount(50_000000000000), 3, pair.clone())
.await;

// make an address to send the eth to
// rand address bytes
let mut rand_bytes = vec![0; 32];
OsRng.fill_bytes(&mut rand_bytes);
let external_address = ExternalAddress::new(rand_bytes).unwrap();

// now that we have our pools, we can try to swap
let mut block_hash = BlockHash([0; 32]);
OsRng.fill_bytes(&mut block_hash.0);
let batch = Batch {
network: NetworkId::Monero,
id: batch_id,
block: block_hash,
instructions: vec![InInstructionWithBalance {
instruction: InInstruction::Dex(DexCall::Swap(coin2, external_address, Amount(1))),
balance: Balance { coin: coin1, amount: Amount(20_000000000000) },
}],
};

let block = provide_batch(batch).await;
let mut events = serai.dex_events(block).await.unwrap();
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));

// we should have only 1 swap event.
assert_eq!(events.len(), 1);

let path = BoundedVec::truncate_from(vec![coin1, Coin::Serai, coin2]);
assert_eq!(
events,
vec![DexEvent::SwapExecuted {
who: IN_INSTRUCTION_EXECUTOR.into(),
send_to: IN_INSTRUCTION_EXECUTOR.into(),
path,
amount_in: 20_000000000000,
amount_out: 11066655622377
}]
);
// coin -> coin(XMR -> ETH)
{
// make an out address
let out_address = OutAddress::External(ExternalAddress::new(rand_bytes.clone()).unwrap());

// amount is the min out amount
let out_balance = Balance { coin: coin2, amount: Amount(1) };

// now that we have our pools, we can try to swap
let mut block_hash = BlockHash([0; 32]);
OsRng.fill_bytes(&mut block_hash.0);
let batch = Batch {
network: NetworkId::Monero,
id: coin1_batch_id,
block: block_hash,
instructions: vec![InInstructionWithBalance {
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address)),
balance: Balance { coin: coin1, amount: Amount(20_000000000000) },
}],
};

let block = provide_batch(batch).await;
coin1_batch_id += 1;
let mut events = serai.dex_events(block).await.unwrap();
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));

// we should have only 1 swap event.
assert_eq!(events.len(), 1);

let path = BoundedVec::truncate_from(vec![coin1, Coin::Serai, coin2]);
assert_eq!(
events,
vec![DexEvent::SwapExecuted {
who: IN_INSTRUCTION_EXECUTOR.into(),
send_to: IN_INSTRUCTION_EXECUTOR.into(),
path,
amount_in: 20_000000000000,
amount_out: 11066655622377
}]
);
}

// coin -> coin(with internal address, ETH -> XMR)
{
// make an out address
let out_address =
OutAddress::Serai(SeraiAddress::new(rand_bytes.clone().try_into().unwrap()));

// amount is the min out amount
let out_balance = Balance { coin: coin1, amount: Amount(1) };

// now that we have our pools, we can try to swap
let mut block_hash = BlockHash([0; 32]);
OsRng.fill_bytes(&mut block_hash.0);
let batch = Batch {
network: NetworkId::Ethereum,
id: coin2_batch_id,
block: block_hash,
instructions: vec![InInstructionWithBalance {
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address.clone())),
balance: Balance { coin: coin2, amount: Amount(20_000000000000) },
}],
};

let block = provide_batch(batch).await;
let mut events = serai.dex_events(block).await.unwrap();
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));

// we should have only 1 swap event.
assert_eq!(events.len(), 1);

let path = BoundedVec::truncate_from(vec![coin2, Coin::Serai, coin1]);
assert_eq!(
events,
vec![DexEvent::SwapExecuted {
who: IN_INSTRUCTION_EXECUTOR.into(),
send_to: out_address.as_native().unwrap().into(),
path,
amount_in: 20_000000000000,
amount_out: 26440798801319
}]
);
}

// coin -> SRI(XMR -> SRI)
{
// make an out address
let out_address = OutAddress::Serai(SeraiAddress::new(rand_bytes.try_into().unwrap()));

// amount is the min out amount
let out_balance = Balance { coin: Coin::Serai, amount: Amount(1) };

// now that we have our pools, we can try to swap
let mut block_hash = BlockHash([0; 32]);
OsRng.fill_bytes(&mut block_hash.0);
let batch = Batch {
network: NetworkId::Monero,
id: coin1_batch_id,
block: block_hash,
instructions: vec![InInstructionWithBalance {
instruction: InInstruction::Dex(DexCall::Swap(out_balance, out_address.clone())),
balance: Balance { coin: coin1, amount: Amount(10_000000000000) },
}],
};

let block = provide_batch(batch).await;
let mut events = serai.dex_events(block).await.unwrap();
events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. }));

// we should have only 1 swap event.
assert_eq!(events.len(), 1);

let path = BoundedVec::truncate_from(vec![coin1, Coin::Serai]);
assert_eq!(
events,
vec![DexEvent::SwapExecuted {
who: IN_INSTRUCTION_EXECUTOR.into(),
send_to: out_address.as_native().unwrap().into(),
path,
amount_in: 10_000000000000,
amount_out: 10711005507065
}]
);
}

// TODO: check balances?
}
Expand Down
97 changes: 62 additions & 35 deletions substrate/in-instructions/pallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ pub mod pallet {
InstructionFailure { network: NetworkId, id: u32, index: u32 },
}

#[pallet::error]
pub enum Error<T> {
/// Coin and OutAddress types don't match.
InvalidAddressForCoin,
}

#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);

Expand Down Expand Up @@ -116,9 +122,9 @@ pub mod pallet {
origin.clone().into(),
path,
half,
1, // minimum out, so that we accept whatever we get.
1, // minimum out, so we accept whatever we get.
IN_INSTRUCTION_EXECUTOR.into(),
false,
true,
)?;

// get how much we got for our swap
Expand All @@ -140,57 +146,78 @@ pub mod pallet {

// TODO: minimums are set to 1 above to guarantee successful adding liq call.
// Ideally we either get this info from user or send the leftovers back to user.
// If leftovers stay on our IIExecutor account, they can accumulate and would
// give us wrong SRI amounts next time another users call here(since we know
// how much to add liq by checking the balance on it). Which then would
// make addling liq fail. So Let's send the leftovers back to user.
// Let's send the leftovers back to user for now.
let coin_balance = Tokens::<T>::balance(coin, IN_INSTRUCTION_EXECUTOR);
let sri_balance =
BalancesPallet::<T>::free_balance(PublicKey::from(IN_INSTRUCTION_EXECUTOR));

if coin_balance != 0 {
Tokens::<T>::transfer(origin.clone().into(), coin, address, coin_balance)?;
Tokens::<T>::transfer_keep_alive(
origin.clone().into(),
coin,
address,
coin_balance,
)?;
}
if sri_balance != 0 {
// unwrap here. First, it doesn't panic, second we have no choice but to empty
// IIE account.
BalancesPallet::<T>::transfer_allow_death(origin.into(), address, sri_balance)
// unwrap here. First, it doesn't panic for full amount,
// second we should empty IIE account anyway.
BalancesPallet::<T>::transfer_keep_alive(origin.into(), address, sri_balance)
.unwrap();
}

// TODO: ideally we would get the coin and sri balances again and
// make sure they are 0. But we already made 3 calls and that
// would make 5. should we do it?
}
DexCall::Swap(coin2, address, amount_out_min) => {
let origin = RawOrigin::Signed(IN_INSTRUCTION_EXECUTOR.into());
DexCall::Swap(out_balance, out_address) => {
let send_to_external = !out_address.is_native();
let native_coin = out_balance.coin.is_native();

// we can't send native coin to external chain
if native_coin && send_to_external {
Err(Error::<T>::InvalidAddressForCoin)?;
}

// mint the given coin on our account
Tokens::<T>::mint(IN_INSTRUCTION_EXECUTOR, instruction.balance);

// do the swap on our account
let path =
BoundedVec::truncate_from(vec![instruction.balance.coin, Coin::Serai, coin2]);
// get the path
let mut path = vec![instruction.balance.coin, Coin::Serai];
if !native_coin {
path.push(out_balance.coin);
}

// get the swap address
// if the address is internal, we can directly swap to it.
// if not, we swap to ourselves and burn the coins to send them back
// on the external chain.
let send_to = if send_to_external {
IN_INSTRUCTION_EXECUTOR
} else {
out_address.clone().as_native().unwrap()
};

// do the swap
let origin = RawOrigin::Signed(IN_INSTRUCTION_EXECUTOR.into());
Dex::<T>::swap_exact_tokens_for_tokens(
origin.clone().into(),
path,
origin.into(),
BoundedVec::truncate_from(path),
instruction.balance.amount.0,
amount_out_min.0,
IN_INSTRUCTION_EXECUTOR.into(),
false,
out_balance.amount.0,
send_to.into(),
true,
)?;

// see how much we got
let coin2_balance = Tokens::<T>::balance(coin2, IN_INSTRUCTION_EXECUTOR);

// burn the received coins so that they sent back to the user
let balance = Balance { coin: coin2, amount: Amount(coin2_balance) };

// TODO: data shouldn't come here from processor just to go back to it.
Tokens::<T>::burn_internal(
IN_INSTRUCTION_EXECUTOR,
balance,
OutInstruction { address, data: None },
)?;
// if it is requested to an external address.
if send_to_external {
// see how much we got
let coin2_balance = Tokens::<T>::balance(out_balance.coin, IN_INSTRUCTION_EXECUTOR);
let balance = Balance { coin: out_balance.coin, amount: Amount(coin2_balance) };
// TODO: data shouldn't come here from processor just to go back to it.
Tokens::<T>::burn_internal(
IN_INSTRUCTION_EXECUTOR,
balance,
OutInstruction { address: out_address.as_external().unwrap(), data: None },
)?;
}
}
}
}
Expand Down
Loading

0 comments on commit 187ef15

Please sign in to comment.