use rand_core::{RngCore, OsRng}; use scale::Encode; use sp_core::{Pair as PairTrait, bounded_vec::BoundedVec, hashing::blake2_256}; use serai_runtime::in_instructions::primitives::DexCall; use serai_client::{ primitives::{ Amount, NetworkId, Coin, Balance, BlockHash, insecure_pair_from_name, ExternalAddress, SeraiAddress, PublicKey, }, in_instructions::primitives::{ InInstruction, InInstructionWithBalance, Batch, IN_INSTRUCTION_EXECUTOR, OutAddress, }, dex::DexEvent, Serai, }; mod common; use common::{ in_instructions::{provide_batch, mint_coin}, dex::{add_liquidity as common_add_liquidity, swap as common_swap}, }; // TODO: Calculate all constants in the following tests // TODO: Check LP token, coin balances // TODO: Modularize common code // TODO: Check Transfer events serai_test!( create_pool: (|serai: Serai| async move { let block = serai.finalized_block_by_number(0).await.unwrap().unwrap().hash(); let events = serai.as_of(block).dex().events().await.unwrap(); assert_eq!( events, vec![ DexEvent::PoolCreated { pool_id: Coin::Bitcoin, pool_account: PublicKey::from_raw(blake2_256(&Coin::Bitcoin.encode())), lp_token: Coin::Bitcoin, }, DexEvent::PoolCreated { pool_id: Coin::Ether, pool_account: PublicKey::from_raw(blake2_256(&Coin::Ether.encode())), lp_token: Coin::Ether, }, DexEvent::PoolCreated { pool_id: Coin::Dai, pool_account: PublicKey::from_raw(blake2_256(&Coin::Dai.encode())), lp_token: Coin::Dai, }, DexEvent::PoolCreated { pool_id: Coin::Monero, pool_account: PublicKey::from_raw(blake2_256(&Coin::Monero.encode())), lp_token: Coin::Monero, }, ] ); }) add_liquidity: (|serai: Serai| async move { let coin = Coin::Monero; let pair = insecure_pair_from_name("Ferdie"); // mint sriXMR in the account so that we can add liq. // Ferdie account is already pre-funded with SRI. mint_coin( &serai, Balance { coin, amount: Amount(100_000_000_000_000) }, NetworkId::Monero, 0, pair.clone().public().into(), ) .await; // add liquidity let coin_amount = Amount(50_000_000_000_000); let sri_amount = Amount(50_000_000_000_000); let block = common_add_liquidity(&serai, coin, coin_amount, sri_amount, 0, pair.clone() ).await; // get only the add liq events let mut events = serai.as_of(block).dex().events().await.unwrap(); events.retain(|e| matches!(e, DexEvent::LiquidityAdded { .. })); assert_eq!( events, vec![DexEvent::LiquidityAdded { who: pair.public(), mint_to: pair.public(), pool_id: Coin::Monero, coin_amount: coin_amount.0, sri_amount: sri_amount.0, lp_token: Coin::Monero, lp_token_minted: 49_999999990000 }] ); }) // Tests coin -> SRI and SRI -> coin swaps. swap_coin_to_sri: (|serai: Serai| async move { let coin = Coin::Ether; let pair = insecure_pair_from_name("Ferdie"); // mint sriXMR in the account so that we can add liq. // Ferdie account is already pre-funded with SRI. mint_coin( &serai, Balance { coin, amount: Amount(100_000_000_000_000) }, NetworkId::Ethereum, 0, pair.clone().public().into(), ) .await; // add liquidity common_add_liquidity(&serai, coin, Amount(50_000_000_000_000), Amount(50_000_000_000_000), 0, pair.clone() ).await; // now that we have our liquid pool, swap some coin to SRI. let mut amount_in = Amount(25_000_000_000_000); let mut block = common_swap(&serai, coin, Coin::Serai, amount_in, Amount(1), 1, pair.clone()) .await; // get only the swap events let mut events = serai.as_of(block).dex().events().await.unwrap(); events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. })); let mut path = BoundedVec::try_from(vec![coin, Coin::Serai]).unwrap(); assert_eq!( events, vec![DexEvent::SwapExecuted { who: pair.clone().public(), send_to: pair.public(), path, amount_in: amount_in.0, amount_out: 16633299966633 }] ); // now swap some SRI to coin amount_in = Amount(10_000_000_000_000); block = common_swap(&serai, Coin::Serai, coin, amount_in, Amount(1), 2, pair.clone()).await; // get only the swap events let mut events = serai.as_of(block).dex().events().await.unwrap(); events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. })); path = BoundedVec::try_from(vec![Coin::Serai, coin]).unwrap(); assert_eq!( events, vec![DexEvent::SwapExecuted { who: pair.clone().public(), send_to: pair.public(), path, amount_in: amount_in.0, amount_out: 17254428681101 }] ); }) swap_coin_to_coin: (|serai: Serai| async move { let coin1 = Coin::Monero; let coin2 = Coin::Dai; let pair = insecure_pair_from_name("Ferdie"); // mint coins mint_coin( &serai, Balance { coin: coin1, amount: Amount(100_000_000_000_000) }, NetworkId::Monero, 0, pair.clone().public().into(), ) .await; mint_coin( &serai, Balance { coin: coin2, amount: Amount(100_000_000_000_000) }, NetworkId::Ethereum, 0, pair.clone().public().into(), ) .await; // add liquidity to pools common_add_liquidity(&serai, coin1, Amount(50_000_000_000_000), Amount(50_000_000_000_000), 0, pair.clone() ).await; common_add_liquidity(&serai, coin2, Amount(50_000_000_000_000), Amount(50_000_000_000_000), 1, pair.clone() ).await; // swap coin1 -> coin2 let amount_in = Amount(25_000_000_000_000); let block = common_swap(&serai, coin1, coin2, amount_in, Amount(1), 2, pair.clone()).await; // get only the swap events let mut events = serai.as_of(block).dex().events().await.unwrap(); events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. })); let path = BoundedVec::try_from(vec![coin1, Coin::Serai, coin2]).unwrap(); assert_eq!( events, vec![DexEvent::SwapExecuted { who: pair.clone().public(), send_to: pair.public(), path, amount_in: amount_in.0, amount_out: 12453103964435, }] ); }) add_liquidity_in_instructions: (|serai: Serai| async move { let coin = Coin::Bitcoin; let pair = insecure_pair_from_name("Ferdie"); let mut batch_id = 0; // mint sriBTC in the account so that we can add liq. // Ferdie account is already pre-funded with SRI. mint_coin( &serai, Balance { coin, amount: Amount(100_000_000_000_000) }, NetworkId::Bitcoin, batch_id, pair.clone().public().into(), ) .await; batch_id += 1; // add liquidity common_add_liquidity(&serai, coin, Amount(50_000_000_000_000), Amount(50_000_000_000_000), 0, pair.clone() ).await; // now that we have our liquid SRI/BTC pool, we can add more liquidity to it via an // InInstruction let mut block_hash = BlockHash([0; 32]); OsRng.fill_bytes(&mut block_hash.0); let batch = Batch { network: NetworkId::Bitcoin, id: batch_id, block: block_hash, instructions: vec![InInstructionWithBalance { instruction: InInstruction::Dex(DexCall::SwapAndAddLiquidity(pair.public().into())), balance: Balance { coin: Coin::Bitcoin, amount: Amount(20_000_000_000_000) }, }], }; let block = provide_batch(&serai, batch).await; let mut events = serai.as_of(block).dex().events().await.unwrap(); events.retain(|e| matches!(e, DexEvent::LiquidityAdded { .. })); assert_eq!( events, vec![DexEvent::LiquidityAdded { who: IN_INSTRUCTION_EXECUTOR.into(), mint_to: pair.public(), pool_id: Coin::Bitcoin, coin_amount: 10_000_000_000_000, // half of sent amount sri_amount: 6_947_918_403_646, lp_token: Coin::Bitcoin, lp_token_minted: 8333333333332 }] ); }) swap_in_instructions: (|serai: Serai| async move { let coin1 = Coin::Monero; let coin2 = Coin::Ether; let pair = insecure_pair_from_name("Ferdie"); let mut coin1_batch_id = 0; let mut coin2_batch_id = 0; // mint coins mint_coin( &serai, Balance { coin: coin1, amount: Amount(100_000_000_000_000) }, NetworkId::Monero, coin1_batch_id, pair.clone().public().into(), ) .await; coin1_batch_id += 1; mint_coin( &serai, Balance { coin: coin2, amount: Amount(100_000_000_000_000) }, NetworkId::Ethereum, coin2_batch_id, pair.clone().public().into(), ) .await; coin2_batch_id += 1; // add liquidity to pools common_add_liquidity(&serai, coin1, Amount(50_000_000_000_000), Amount(50_000_000_000_000), 0, pair.clone() ).await; common_add_liquidity(&serai, coin2, Amount(50_000_000_000_000), Amount(50_000_000_000_000), 1, pair.clone() ).await; // rand address bytes let mut rand_bytes = vec![0; 32]; OsRng.fill_bytes(&mut rand_bytes); // 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_000_000_000_000) }, }], }; let block = provide_batch(&serai, batch).await; coin1_batch_id += 1; let mut events = serai.as_of(block).dex().events().await.unwrap(); events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. })); let path = BoundedVec::try_from(vec![coin1, Coin::Serai, coin2]).unwrap(); assert_eq!( events, vec![DexEvent::SwapExecuted { who: IN_INSTRUCTION_EXECUTOR.into(), send_to: IN_INSTRUCTION_EXECUTOR.into(), path, amount_in: 20_000_000_000_000, amount_out: 11066655622377 }] ); } // ETH -> sriXMR { // 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_000_000_000_000) }, }], }; let block = provide_batch(&serai, batch).await; let mut events = serai.as_of(block).dex().events().await.unwrap(); events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. })); let path = BoundedVec::try_from(vec![coin2, Coin::Serai, coin1]).unwrap(); assert_eq!( events, vec![DexEvent::SwapExecuted { who: IN_INSTRUCTION_EXECUTOR.into(), send_to: out_address.as_native().unwrap().into(), path, amount_in: 20_000_000_000_000, amount_out: 26440798801319 }] ); } // 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_000_000_000_000) }, }], }; let block = provide_batch(&serai, batch).await; let mut events = serai.as_of(block).dex().events().await.unwrap(); events.retain(|e| matches!(e, DexEvent::SwapExecuted { .. })); let path = BoundedVec::try_from(vec![coin1, Coin::Serai]).unwrap(); assert_eq!( events, vec![DexEvent::SwapExecuted { who: IN_INSTRUCTION_EXECUTOR.into(), send_to: out_address.as_native().unwrap().into(), path, amount_in: 10_000_000_000_000, amount_out: 10711005507065 }] ); } }) );