use std::ops::Range;
use apollo_utils::iterators::IntoElementwise;
use cosmwasm_std::{Coin, Uint128};
use osmosis_test_tube::osmosis_std::types::osmosis::gamm::{
poolmodels::{
balancer::v1beta1::MsgCreateBalancerPool,
stableswap::v1beta1::{MsgCreateStableswapPool, PoolParams as StableSwapPoolParams},
},
v1beta1::{PoolAsset, PoolParams},
};
use osmosis_test_tube::{Account, Gamm, Module, Runner, SigningAccount};
use prop::collection::vec;
use proptest::prelude::*;
use proptest::strategy::{Just, Strategy};
use proptest::{option, prop_compose, proptest};
use crate::const_coin::ConstCoin;
const MAX_SCALE_FACTOR: u64 = 0x7FFF_FFFF_FFFF_FFFF; const MAX_POOL_WEIGHT: u64 = 1048575;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OsmosisPoolType {
Basic,
Balancer {
pool_weights: Vec<u64>,
pool_params: Option<PoolParams>,
},
StableSwap {
scaling_factors: Vec<u64>,
pool_params: Option<StableSwapPoolParams>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConstOsmosisTestPool {
pub liquidity: &'static [ConstCoin],
pub pool_type: OsmosisPoolType,
}
impl ConstOsmosisTestPool {
pub const fn new(liquidity: &'static [ConstCoin], pool_type: OsmosisPoolType) -> Self {
Self {
liquidity,
pool_type,
}
}
}
impl From<ConstOsmosisTestPool> for OsmosisTestPool {
fn from(pool: ConstOsmosisTestPool) -> Self {
Self {
liquidity: pool.liquidity.into_elementwise(),
pool_type: pool.pool_type,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OsmosisTestPool {
pub liquidity: Vec<Coin>,
pub pool_type: OsmosisPoolType,
}
impl OsmosisTestPool {
pub fn new(liquidity: Vec<Coin>, pool_type: OsmosisPoolType) -> Self {
Self {
liquidity,
pool_type,
}
}
pub fn default_pool_params() -> PoolParams {
PoolParams {
swap_fee: "200000000000000000".to_string(),
exit_fee: "0".to_string(),
smooth_weight_change_params: None,
}
}
pub fn default_stableswap_pool_params() -> StableSwapPoolParams {
StableSwapPoolParams {
swap_fee: "200000000000000000".to_string(),
exit_fee: "0".to_string(),
}
}
pub fn create<'a, R: Runner<'a>>(&self, runner: &'a R, signer: &SigningAccount) -> u64 {
let gamm = Gamm::new(runner);
match &self.pool_type {
OsmosisPoolType::Basic => {
gamm.create_basic_pool(&self.liquidity, signer)
.unwrap()
.data
.pool_id
}
OsmosisPoolType::Balancer {
pool_weights,
pool_params,
} => {
gamm.create_balancer_pool(
MsgCreateBalancerPool {
sender: signer.address(),
pool_params: Some(
pool_params
.clone()
.unwrap_or_else(Self::default_pool_params),
),
pool_assets: self
.liquidity
.iter()
.zip(pool_weights.iter())
.map(|(c, weight)| PoolAsset {
token: Some(c.clone().into()),
weight: weight.to_string(),
})
.collect(),
future_pool_governor: "".to_string(),
},
signer,
)
.unwrap()
.data
.pool_id
}
OsmosisPoolType::StableSwap {
scaling_factors,
pool_params,
} => {
gamm.create_stable_swap_pool(
MsgCreateStableswapPool {
sender: signer.address(),
pool_params: Some(
pool_params
.clone()
.unwrap_or_else(Self::default_stableswap_pool_params),
),
initial_pool_liquidity: self
.liquidity
.iter()
.map(|c| c.clone().into())
.collect(),
scaling_factors: scaling_factors.clone(),
future_pool_governor: "".to_string(),
scaling_factor_controller: "".to_string(),
},
signer,
)
.unwrap()
.data
.pool_id
}
}
}
}
pub fn pool_denoms(count: usize) -> impl Strategy<Value = Vec<String>> {
Just(Vec::from_iter(0..8))
.prop_shuffle()
.prop_flat_map(move |x| {
let mut denoms = x
.iter()
.take(count)
.map(|i| format!("denom{}", i))
.collect::<Vec<String>>();
denoms.sort(); Just(denoms)
})
}
pub fn pool_denoms_with_one_common(
count: usize,
input_denoms: Vec<String>,
) -> impl Strategy<Value = Vec<String>> {
pool_denoms(count).prop_flat_map(move |mut denoms| {
if denoms.iter().any(|x| input_denoms.contains(x)) {
Just(denoms)
} else {
let common_denom = input_denoms[0].clone();
denoms[0] = common_denom;
denoms.sort(); Just(denoms)
}
})
}
pub fn pool_denoms_with_one_specific(
count: usize,
specific_denom: String,
) -> impl Strategy<Value = Vec<String>> {
pool_denoms(count).prop_flat_map(move |mut denoms| {
if denoms.iter().any(|x| x == &specific_denom) {
Just(denoms)
} else {
denoms[0].clone_from(&specific_denom);
denoms.sort(); Just(denoms)
}
})
}
pub fn pool_liquidity_amounts(
liquidity_range: Option<Range<u128>>,
) -> impl Strategy<Value = Vec<u128>> {
let liquidity_range = liquidity_range.unwrap_or(0..u64::MAX as u128);
vec(liquidity_range, 2..8)
}
prop_compose! {
pub fn pool_liquidity(liquidity_range: Option<Range<u128>>)(amounts in pool_liquidity_amounts(liquidity_range))(denoms in pool_denoms(amounts.len()), amounts in Just(amounts)) -> Vec<Coin> {
amounts.into_iter().zip(denoms).map(|(amount, denom)| Coin { amount: Uint128::from(amount), denom }).collect()
}
}
prop_compose! {
pub fn pool_liquidity_with_one_common_denom(base_liquidity: Vec<Coin>, liquidity_range: Option<Range<u128>>)
(amounts in pool_liquidity_amounts(liquidity_range))
(
denoms in pool_denoms_with_one_common(amounts.len(),
base_liquidity.iter().map(|x| x.denom.clone()).collect()),
amounts in Just(amounts)
) -> Vec<Coin> {
amounts.into_iter().zip(denoms).map(|(amount, denom)|
Coin { amount: Uint128::from(amount), denom
}).collect()
}
}
prop_compose! {
pub fn pool_liquidity_with_one_specific_denom(specific_denom: String, liquidity_range: Option<Range<u128>>)
(amounts in pool_liquidity_amounts(liquidity_range))
(
denoms in pool_denoms_with_one_specific(amounts.len(), specific_denom.clone()),
amounts in Just(amounts)
) -> Vec<Coin> {
amounts.into_iter().zip(denoms).map(|(amount, denom)|
Coin { amount: Uint128::from(amount), denom
}).collect()
}
}
pub fn scaling_factors(pool_liquidity: &[Coin]) -> impl Strategy<Value = Vec<u64>> {
pool_liquidity
.iter()
.map(|x| {
let max_scale_factor = if x.amount.u128() > MAX_SCALE_FACTOR.into() {
MAX_SCALE_FACTOR
} else {
x.amount.u128() as u64
};
1..max_scale_factor
})
.collect::<Vec<Range<u64>>>()
}
prop_compose! {
pub fn pool_params()(pool_liq in pool_liquidity(None))(scaling_factors in scaling_factors(&pool_liq), pool_liquidity in Just(pool_liq)) -> (Vec<Coin>,Vec<u64>) {
(pool_liquidity, scaling_factors)
}
}
pub fn pool_type(pool_liquidity: &[Coin]) -> impl Strategy<Value = OsmosisPoolType> {
prop_oneof![
Just(OsmosisPoolType::Basic),
vec(1..MAX_POOL_WEIGHT, pool_liquidity.len()).prop_map(|pool_weights| {
OsmosisPoolType::Balancer {
pool_weights,
pool_params: None,
}
}),
scaling_factors(pool_liquidity).prop_map(|scaling_factors| {
OsmosisPoolType::StableSwap {
scaling_factors,
pool_params: None,
}
}),
]
.no_shrink()
}
prop_compose! {
pub fn test_pool_from_liquidity(pool_liquidity: Vec<Coin>)(pool_type in pool_type(&pool_liquidity), liquidity in Just(pool_liquidity)) -> OsmosisTestPool {
OsmosisTestPool {
liquidity,
pool_type,
}
}
}
prop_compose! {
pub fn test_pool(liq_range: Option<Range<u128>>)(pool_liquidity in pool_liquidity(liq_range))(
test_pool in test_pool_from_liquidity(pool_liquidity)
) -> OsmosisTestPool {
test_pool
}
}
prop_compose! {
pub fn reward_pool(base_pool: OsmosisTestPool)(pool_liquidity in pool_liquidity_with_one_common_denom(base_pool.liquidity, None))(
pool_type in pool_type(&pool_liquidity), liquidity in Just(pool_liquidity)
) -> OsmosisTestPool {
OsmosisTestPool {
liquidity,
pool_type,
}
}
}
prop_compose! {
pub fn pool_with_denom(specific_denom: String, liq_range: Option<Range<u128>>)(pool_liquidity in pool_liquidity_with_one_specific_denom(specific_denom, liq_range))(
pool_type in pool_type(&pool_liquidity), liquidity in Just(pool_liquidity)
) -> OsmosisTestPool {
OsmosisTestPool {
liquidity,
pool_type,
}
}
}
prop_compose! {
pub fn test_pools(liq_range: Option<Range<u128>>)
((liquidation_target, base_pool) in test_pool(liq_range.clone()).prop_flat_map(|pool| {
(Just(pool.liquidity[0].denom.clone()),Just(pool))
}))
(
reward1_pool in pool_with_denom(liquidation_target.clone(), liq_range.clone()),
reward2_pool in option::of(pool_with_denom(liquidation_target, liq_range.clone())),
base_pool in Just(base_pool)
) -> (OsmosisTestPool, OsmosisTestPool, Option<OsmosisTestPool>) {
(base_pool, reward1_pool, reward2_pool)
}
}
#[cfg(test)]
fn assert_test_pool_properties(pool: OsmosisTestPool) {
let OsmosisTestPool {
liquidity,
pool_type,
} = pool;
assert!(liquidity.len() >= 2);
assert!(liquidity.len() <= 8);
assert!(liquidity.iter().all(|liq| liq.amount.u128() > 0));
assert!(liquidity.iter().all(|liq| liq.amount.u128() < u128::MAX));
match pool_type {
OsmosisPoolType::Basic => {}
OsmosisPoolType::Balancer {
pool_weights,
pool_params,
} => {
assert_eq!(pool_weights.len(), liquidity.len());
assert!(pool_weights.iter().all(|weight| weight > &0));
assert!(pool_weights.iter().all(|weight| weight < &MAX_POOL_WEIGHT));
if let Some(params) = pool_params {
assert!(params.exit_fee == *"0");
}
}
OsmosisPoolType::StableSwap {
scaling_factors,
pool_params,
} => {
assert_eq!(scaling_factors.len(), liquidity.len());
assert!(scaling_factors.iter().all(|scale| scale > &0));
assert!(scaling_factors
.iter()
.all(|scale| scale < &MAX_SCALE_FACTOR));
assert!(liquidity
.iter()
.zip(scaling_factors.iter())
.all(|(liq, scale)| (*scale as u128) < liq.amount.u128()));
if let Some(params) = pool_params {
assert!(params.exit_fee == *"0");
}
}
}
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 10000,
max_local_rejects: 0,
..ProptestConfig::default()
})]
#[test]
fn test_pool_params(params in pool_params()) {
let (pool_liquidity, scaling_factors): (Vec<Coin>,Vec<u64>) = params;
assert_eq!(pool_liquidity.len(), scaling_factors.len());
assert!(pool_liquidity.iter().all(|liq| liq.amount.u128() > 0));
assert!(scaling_factors.iter().all(|scale| scale > &0));
assert!(scaling_factors.iter().all(|scale| scale < &MAX_SCALE_FACTOR));
assert!(pool_liquidity.iter().all(|liq| liq.amount.u128() < u128::MAX));
assert!(pool_liquidity.iter().zip(scaling_factors.iter()).all(|(liq, scale)| (*scale as u128) < liq.amount.u128()));
}
#[test]
fn test_denoms(mut denoms in pool_denoms(8)) {
assert!(denoms.len() == 8);
denoms.sort();
assert_eq!(denoms, Vec::from_iter(0..8).iter().map(|i| format!("denom{}", i)).collect::<Vec<String>>());
}
#[test]
fn test_denoms_one_common(denoms in vec(2..8usize,2).prop_flat_map(|counts| pool_denoms(counts[0]).prop_flat_map(move |base_denoms| {
(Just(base_denoms.clone()), pool_denoms_with_one_common(counts[1], base_denoms))
}))) {
let (base_denoms, other_denoms) = denoms;
assert!(base_denoms.iter().any(|denom| other_denoms.contains(denom)));
if base_denoms.len() != other_denoms.len() {
assert_ne!(base_denoms, other_denoms);
}
}
#[test]
fn test_denoms_one_specific(denoms in vec(2..8usize,2).prop_flat_map(|counts| pool_denoms(counts[0]).prop_flat_map(move |base_denoms| {
(Just(base_denoms.clone()), pool_denoms_with_one_specific(counts[1], base_denoms[0].clone()))
}))) {
let (base_denoms, other_denoms) = denoms;
let common_denom = &base_denoms[0];
assert!(other_denoms.contains(common_denom));
}
#[test]
fn test_test_pool(pool in test_pool(None)) {
assert_test_pool_properties(pool);
}
#[test]
fn test_pool_with_denom(pool in pool_with_denom(String::from("requested_denom"), None)) {
assert!(pool.liquidity.iter().any(|liq| liq.denom == "requested_denom"));
assert_test_pool_properties(pool);
}
#[test]
fn test_reward_pool((pool, reward_pool) in test_pool(None).prop_flat_map(|base_pool| (Just(base_pool.clone()), reward_pool(base_pool)))) {
let reward_denoms = reward_pool.liquidity.iter().map(|liq| liq.denom.clone()).collect::<Vec<String>>();
let base_denoms = pool.liquidity.iter().map(|liq| liq.denom.clone()).collect::<Vec<String>>();
assert!(reward_denoms.iter().any(|denom| base_denoms.contains(denom)));
assert_test_pool_properties(pool);
assert_test_pool_properties(reward_pool);
}
}