use crate::osmosis::utils::is_osmosis_lp_token;
use cosmrs::proto::Any;
use cosmwasm_std::Coin;
use osmosis_std::{
shim::Duration,
types::osmosis::{
gamm::v1beta1::{
MsgJoinSwapExternAmountIn, MsgJoinSwapExternAmountInResponse, MsgSwapExactAmountIn,
MsgSwapExactAmountInResponse,
},
lockup::Params as LockupParams,
lockup::{MsgLockTokens, MsgLockTokensResponse},
poolmanager::v1beta1::SwapAmountInRoute,
},
};
use osmosis_test_tube::{Account, OsmosisTestApp, Runner, SigningAccount};
use prost::Message;
use crate::robot::TestRobot;
pub trait OsmosisTestAppRobot<'a>: TestRobot<'a, OsmosisTestApp> {
fn increase_time(&self, seconds: u64) -> &Self {
self.runner().increase_time(seconds);
self
}
fn whitelist_address_for_force_unlock(&self, address: impl Into<String>) -> &Self {
let address = address.into();
let mut whitelist = self.get_force_unlock_whitelisted_addresses();
if !whitelist.contains(&address) {
whitelist.push(address);
let pset = LockupParams {
force_unlock_allowed_addresses: whitelist,
};
self.runner()
.set_param_set(
"lockup",
Any {
type_url: LockupParams::TYPE_URL.to_string(),
value: pset.encode_to_vec(),
},
)
.unwrap();
}
self
}
fn get_force_unlock_whitelisted_addresses(&self) -> Vec<String> {
let pset: LockupParams = self
.runner()
.get_param_set("lockup", LockupParams::TYPE_URL)
.unwrap();
pset.force_unlock_allowed_addresses
}
}
pub trait OsmosisTestRobot<'a>: TestRobot<'a, OsmosisTestApp> {
fn join_pool_swap_extern_amount_in(
&self,
signer: &SigningAccount,
pool_id: u64,
coin: Coin,
min_out: Option<u128>,
) -> &Self {
let min_out = min_out.unwrap_or(1);
let msg = MsgJoinSwapExternAmountIn {
pool_id,
token_in: Some(coin.into()),
sender: signer.address(),
share_out_min_amount: format!("{min_out}"),
};
self.runner()
.execute::<_, MsgJoinSwapExternAmountInResponse>(
msg,
MsgJoinSwapExternAmountIn::TYPE_URL,
signer,
)
.unwrap();
self
}
fn swap_exact_amount_in(
&self,
signer: &SigningAccount,
pool_id: u64,
token_in: Coin,
token_out_denom: impl Into<String>,
min_out: Option<u128>,
) -> &Self {
let min_out = min_out.unwrap_or(1);
let msg = MsgSwapExactAmountIn {
routes: vec![SwapAmountInRoute {
pool_id,
token_out_denom: token_out_denom.into(),
}],
token_in: Some(token_in.into()),
sender: signer.address(),
token_out_min_amount: format!("{min_out}"),
};
self.runner()
.execute::<_, MsgSwapExactAmountInResponse>(msg, MsgSwapExactAmountIn::TYPE_URL, signer)
.unwrap();
self
}
fn lock_tokens(&self, signer: &SigningAccount, coin: Coin, duration: u32) -> &Self {
if !is_osmosis_lp_token(&coin.denom) {
panic!("Only LP shares can be locked");
}
let msg = MsgLockTokens {
coins: vec![coin.into()],
duration: Some(Duration {
seconds: duration as i64,
nanos: 0,
}),
owner: signer.address(),
};
self.runner()
.execute::<_, MsgLockTokensResponse>(msg, MsgLockTokens::TYPE_URL, signer)
.unwrap();
self
}
}
#[cfg(test)]
mod tests {
use apollo_utils::iterators::IntoElementwise;
use cosmwasm_std::Coin;
use osmosis_test_tube::{FeeSetting, Gamm, Module, OsmosisTestApp};
use crate::const_coin::ConstCoin;
use super::*;
struct TestingRobot<'a>(&'a OsmosisTestApp);
impl<'a> TestRobot<'a, OsmosisTestApp> for TestingRobot<'a> {
fn runner(&self) -> &'a OsmosisTestApp {
self.0
}
}
impl<'a> OsmosisTestRobot<'a> for TestingRobot<'a> {}
impl<'a> OsmosisTestAppRobot<'a> for TestingRobot<'a> {}
const INITIAL_BALANCES: &[ConstCoin] = &[
ConstCoin::new(100_000_000_000_000_000u128, "uatom"),
ConstCoin::new(100_000_000_000_000_000u128, "uosmo"),
];
#[test]
fn test_get_and_set_force_withdraw_whitelist() {
let app = OsmosisTestApp::new();
let robot = TestingRobot(&app);
let whitelist = robot.get_force_unlock_whitelisted_addresses();
assert!(whitelist.is_empty());
let account = app.init_account(&[]).unwrap();
robot.whitelist_address_for_force_unlock(account.address());
let whitelist = robot.get_force_unlock_whitelisted_addresses();
assert_eq!(whitelist, vec![account.address()]);
robot.whitelist_address_for_force_unlock(account.address());
let whitelist = robot.get_force_unlock_whitelisted_addresses();
assert_eq!(whitelist, vec![account.address()]);
let account2 = app.init_account(&[]).unwrap();
robot.whitelist_address_for_force_unlock(account2.address());
let whitelist = robot.get_force_unlock_whitelisted_addresses();
assert_eq!(whitelist, vec![account.address(), account2.address()]);
}
#[test]
fn test_join_pool_swap_extern_amount_in() {
let app = OsmosisTestApp::new();
let account = app
.init_account(&INITIAL_BALANCES.into_elementwise())
.unwrap();
let gamm = Gamm::new(&app);
let pool_id = gamm
.create_basic_pool(
&[
Coin::new(1_000_000_000, "uosmo"),
Coin::new(1_000_000_000, "uatom"),
],
&account,
)
.unwrap()
.data
.pool_id;
let robot = TestingRobot(&app);
robot
.join_pool_swap_extern_amount_in(
&account,
pool_id,
Coin::new(1_000_000_000, "uosmo"),
None,
)
.assert_native_token_balance_gt(
account.address(),
format!("gamm/pool/{pool_id}"),
0u128,
);
}
#[test]
fn test_swap_exact_amount_in() {
let app = OsmosisTestApp::new();
const GAS_AMOUNT: u128 = 1_000_000;
let fee_setting: FeeSetting = FeeSetting::Custom {
amount: Coin {
denom: "uosmo".to_string(),
amount: GAS_AMOUNT.into(),
},
gas_limit: 20_000_000,
};
let account1 = app
.init_account(&INITIAL_BALANCES.into_elementwise())
.unwrap()
.with_fee_setting(fee_setting.clone());
let account2 = app
.init_account(&INITIAL_BALANCES.into_elementwise())
.unwrap()
.with_fee_setting(fee_setting);
let initial_balance = INITIAL_BALANCES
.iter()
.find(|c| c.denom == "uatom")
.unwrap()
.amount
.u128();
let gamm = Gamm::new(&app);
let pool_id = gamm
.create_basic_pool(
&[
Coin::new(1_000_000_000, "uosmo"),
Coin::new(1_000_000_000, "uatom"),
],
&account1,
)
.unwrap()
.data
.pool_id;
let swap_amount = 1_000_000_000u128;
let robot = TestingRobot(&app);
robot
.swap_exact_amount_in(
&account2,
pool_id,
Coin::new(swap_amount, "uosmo"),
"uatom",
None,
)
.assert_native_token_balance_eq(
account2.address(),
"uosmo",
initial_balance - swap_amount - GAS_AMOUNT,
)
.assert_native_token_balance_gt(
account2.address(),
"uatom",
initial_balance,
)
.assert_native_token_balance_lt(
account2.address(),
"uatom",
initial_balance + swap_amount,
);
}
}