diff --git a/.github/workflows/ci-test-fmt-check.yaml b/.github/workflows/ci-test-fmt-check.yaml index b7b9c9ac..590ff9b8 100644 --- a/.github/workflows/ci-test-fmt-check.yaml +++ b/.github/workflows/ci-test-fmt-check.yaml @@ -14,8 +14,8 @@ env: CARGO_TERM_COLOR: always jobs: - test_and_check: - name: Test and check + test_and_check-token_factory_feature: + name: Test and check Token factory feature runs-on: ubuntu-latest steps: @@ -43,11 +43,11 @@ jobs: override: true components: rustfmt, clippy - - name: Run cargo-tarpaulin + - name: Run cargo-tarpaulin token_factory feature uses: actions-rs/tarpaulin@v0.1 with: version: "0.15.0" - args: '--features "injective token_factory" --locked -- --test-threads 4' + args: '--features "token_factory" --locked -- --test-threads 4' - name: Upload to codecov.io uses: codecov/codecov-action@v3 @@ -75,3 +75,53 @@ jobs: chmod +x ./scripts/build_schemas.sh ./scripts/build_schemas.sh true shell: bash + + test_and_check-token_injective_feature: + name: Test and check Injective feature + runs-on: ubuntu-latest + + steps: + # Cancel any existing runs to save on CI time + # - name: Cancel Previous Runs + # uses: styfle/cancel-workflow-action@0.9.1 + # with: + # access_token: ${{ github.token }} + # Checkout code, with submodules using PAT + - name: Checkout sources + uses: actions/checkout@v3 + + # Use Rust Cache to speed up subsequent jobs with no cargo lock changes + - name: Use Rust cache + uses: Swatinem/rust-cache@v2 + with: + key: "test" + + # Install rust + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.70.0 + override: true + components: rustfmt, clippy + + - name: Run cargo-tarpaulin injective feature + uses: actions-rs/tarpaulin@v0.1 + with: + version: "0.15.0" + args: '--features "injective" --locked -- --test-threads 4' + + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: --locked -- -D warnings + + #- name: Run cosmwasm linter + # run: cargo dylint cw_lint --workspace + + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check diff --git a/Cargo.lock b/Cargo.lock index 91b1f7a9..3719a617 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1434,13 +1434,15 @@ dependencies = [ [[package]] name = "terraswap-pair" -version = "1.3.2" +version = "1.3.3" dependencies = [ "cosmwasm-schema", "cosmwasm-std", + "cw-multi-test", "cw-storage-plus", "cw2", "cw20", + "cw20-base", "integer-sqrt", "protobuf", "schemars", @@ -1542,7 +1544,7 @@ checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" [[package]] name = "vault" -version = "1.2.4" +version = "1.2.6" dependencies = [ "cosmwasm-schema", "cosmwasm-std", @@ -1583,7 +1585,7 @@ dependencies = [ [[package]] name = "vault_router" -version = "1.1.5" +version = "1.1.6" dependencies = [ "cosmwasm-schema", "cosmwasm-std", diff --git a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs index bced40b2..bb582df2 100644 --- a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs +++ b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/commands.rs @@ -5,15 +5,25 @@ use cosmwasm_std::{ use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; use crate::contract::{MAX_AMP, MAX_AMP_CHANGE, MIN_AMP, MIN_RAMP_BLOCKS}; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use cosmwasm_std::coins; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use white_whale::pool_network::asset::is_factory_token; use white_whale::pool_network::asset::{ Asset, AssetInfo, AssetInfoRaw, TrioInfoRaw, MINIMUM_LIQUIDITY_AMOUNT, }; #[cfg(feature = "token_factory")] use white_whale::pool_network::denom::{Coin, MsgBurn, MsgMint}; +#[cfg(feature = "injective")] +use white_whale::pool_network::denom_injective::{Coin, MsgBurn, MsgMint}; #[cfg(feature = "osmosis_token_factory")] use white_whale::pool_network::denom_osmosis::{Coin, MsgBurn, MsgMint}; use white_whale::pool_network::swap; @@ -672,7 +682,11 @@ fn mint_lp_token_msg( sender: String, amount: Uint128, ) -> Result, ContractError> { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] if is_factory_token(liquidity_token.as_str()) { let mut messages = vec![]; messages.push(>::into(MsgMint { @@ -699,7 +713,11 @@ fn mint_lp_token_msg( })]) } - #[cfg(all(not(feature = "token_factory"), not(feature = "osmosis_token_factory")))] + #[cfg(all( + not(feature = "token_factory"), + not(feature = "osmosis_token_factory"), + not(feature = "injective") + ))] Ok(vec![CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: liquidity_token, msg: to_binary(&Cw20ExecuteMsg::Mint { recipient, amount })?, @@ -714,7 +732,11 @@ fn burn_lp_token_msg( sender: String, amount: Uint128, ) -> Result { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] if is_factory_token(liquidity_token.as_str()) { Ok(>::into(MsgBurn { sender, @@ -731,7 +753,11 @@ fn burn_lp_token_msg( })) } - #[cfg(all(not(feature = "token_factory"), not(feature = "osmosis_token_factory")))] + #[cfg(all( + not(feature = "token_factory"), + not(feature = "osmosis_token_factory"), + not(feature = "injective") + ))] Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: liquidity_token, msg: to_binary(&Cw20ExecuteMsg::Burn { amount })?, diff --git a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs index 9c4674fa..8afe24ce 100644 --- a/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs +++ b/contracts/liquidity_hub/pool-network/stableswap_3pool/src/helpers.rs @@ -6,11 +6,17 @@ use cosmwasm_std::{ use cw20::MinterResponse; use cw_storage_plus::Item; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use cosmwasm_std::CosmosMsg; use white_whale::pool_network::asset::{is_factory_token, Asset, AssetInfo, AssetInfoRaw}; #[cfg(feature = "token_factory")] use white_whale::pool_network::denom::MsgCreateDenom; +#[cfg(feature = "injective")] +use white_whale::pool_network::denom_injective::MsgCreateDenom; #[cfg(feature = "osmosis_token_factory")] use white_whale::pool_network::denom_osmosis::MsgCreateDenom; use white_whale::pool_network::querier::query_token_info; @@ -207,7 +213,11 @@ pub fn instantiate_fees( /// Gets the total supply of the given liquidity token pub fn get_total_share(deps: &Deps, liquidity_token: String) -> StdResult { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] let total_share = if is_factory_token(liquidity_token.as_str()) { //bank query total deps.querier.query_supply(&liquidity_token)?.amount @@ -218,7 +228,11 @@ pub fn get_total_share(deps: &Deps, liquidity_token: String) -> StdResult>::into( MsgCreateDenom { diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/Cargo.toml b/contracts/liquidity_hub/pool-network/terraswap_pair/Cargo.toml index 4f1f403d..0111fe44 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/Cargo.toml +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "terraswap-pair" -version = "1.3.2" +version = "1.3.3" authors = [ "Terraform Labs, PTE.", "DELIGHT LABS", @@ -45,3 +45,7 @@ thiserror.workspace = true protobuf.workspace = true white-whale.workspace = true cosmwasm-schema.workspace = true + +[dev-dependencies] +cw-multi-test.workspace = true +cw20-base.workspace = true \ No newline at end of file diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs index afb99946..b6753f98 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs @@ -4,9 +4,17 @@ use cosmwasm_std::{ }; use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use cosmwasm_std::coins; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use white_whale::pool_network::asset::is_factory_token; use white_whale::pool_network::asset::{ get_total_share, has_factory_token, Asset, AssetInfo, AssetInfoRaw, PairInfoRaw, @@ -14,6 +22,8 @@ use white_whale::pool_network::asset::{ }; #[cfg(feature = "token_factory")] use white_whale::pool_network::denom::{Coin, MsgBurn, MsgMint}; +#[cfg(feature = "injective")] +use white_whale::pool_network::denom_injective::{Coin, MsgBurn, MsgMint}; #[cfg(feature = "osmosis_token_factory")] use white_whale::pool_network::denom_osmosis::{Coin, MsgBurn, MsgMint}; use white_whale::pool_network::pair::{Config, Cw20HookMsg, FeatureToggle, PoolFee}; @@ -577,7 +587,11 @@ fn mint_lp_token_msg( sender: String, amount: Uint128, ) -> Result, ContractError> { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] if is_factory_token(liquidity_token.as_str()) { let mut messages = vec![]; messages.push(>::into(MsgMint { @@ -604,7 +618,11 @@ fn mint_lp_token_msg( })]) } - #[cfg(all(not(feature = "token_factory"), not(feature = "osmosis_token_factory")))] + #[cfg(all( + not(feature = "token_factory"), + not(feature = "osmosis_token_factory"), + not(feature = "injective") + ))] Ok(vec![CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: liquidity_token, msg: to_binary(&Cw20ExecuteMsg::Mint { recipient, amount })?, @@ -619,7 +637,11 @@ fn burn_lp_token_msg( sender: String, amount: Uint128, ) -> Result { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] if is_factory_token(liquidity_token.as_str()) { Ok(>::into(MsgBurn { sender, @@ -635,7 +657,11 @@ fn burn_lp_token_msg( funds: vec![], })) } - #[cfg(all(not(feature = "token_factory"), not(feature = "osmosis_token_factory")))] + #[cfg(all( + not(feature = "token_factory"), + not(feature = "osmosis_token_factory"), + not(feature = "injective") + ))] Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: liquidity_token, msg: to_binary(&Cw20ExecuteMsg::Burn { amount })?, diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/contract.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/contract.rs index 7d2719b5..25145ae5 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/contract.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/contract.rs @@ -248,11 +248,18 @@ pub fn migrate(mut deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result>::into( MsgCreateDenom { diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/migrations.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/migrations.rs index 22acefcc..ef64bbdf 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/migrations.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/migrations.rs @@ -1,8 +1,12 @@ #![cfg(not(tarpaulin_include))] use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, CanonicalAddr, Decimal, DepsMut, StdError, Uint128}; +#[cfg(not(feature = "injective"))] +use cosmwasm_std::Uint128; +use cosmwasm_std::{Addr, CanonicalAddr, Decimal, DepsMut, StdError}; use cw_storage_plus::Item; +#[cfg(not(feature = "injective"))] use schemars::JsonSchema; +#[cfg(not(feature = "injective"))] use serde::{Deserialize, Serialize}; use white_whale::fee::Fee; @@ -11,8 +15,11 @@ use white_whale::pool_network::asset::{AssetInfo, AssetInfoRaw, PairType}; use white_whale::pool_network::pair::{Config, FeatureToggle}; use crate::helpers::instantiate_fees; -use crate::state::{ALL_TIME_BURNED_FEES, CONFIG, PAIR_INFO}; +#[cfg(not(feature = "injective"))] +use crate::state::PAIR_INFO; +use crate::state::{ALL_TIME_BURNED_FEES, CONFIG}; +#[cfg(not(feature = "injective"))] /// Migrate state of the factory from PascalCase to snake_case for the following items: /// [`PairInfoRaw`], [`PairInfo`] /// as identified by commit c8d8462c6933b93245acdc8abbe303287fdc1951 which changed the structs to use @@ -67,6 +74,7 @@ pub fn migrate_to_v110(deps: DepsMut) -> Result<(), StdError> { Ok(()) } +#[cfg(not(feature = "injective"))] pub fn migrate_to_v120(deps: DepsMut) -> Result<(), StdError> { #[cw_serde] struct ConfigV110 { @@ -116,6 +124,7 @@ pub fn migrate_to_v120(deps: DepsMut) -> Result<(), StdError> { Ok(()) } +#[cfg(not(feature = "injective"))] /// Migrate to the StableSwap deployment /// /// Default to a ConstantProduct pool @@ -158,3 +167,110 @@ pub fn migrate_to_v130(deps: DepsMut) -> Result<(), StdError> { Ok(()) } + +#[cfg(feature = "injective")] +/// Migrates the state from v1.1.0 to v1.3.x in a single migration +pub fn migrate_to_v13x(deps: DepsMut) -> Result<(), StdError> { + // migration to v1.2.0 + #[cw_serde] + struct ConfigV110 { + pub owner: Addr, + pub fee_collector_addr: Addr, + pub pool_fees: PoolFeeV110, + pub feature_toggle: FeatureToggle, + } + + #[cw_serde] + struct PoolFeeV110 { + pub protocol_fee: Fee, + pub swap_fee: Fee, + } + + const CONFIG_V110: Item = Item::new("config"); + let config_v110 = CONFIG_V110.load(deps.storage)?; + + // Add burn fee to config. Zero fee is used as default. + let config = Config { + owner: config_v110.owner, + fee_collector_addr: config_v110.fee_collector_addr, + pool_fees: pool_network::pair::PoolFee { + protocol_fee: config_v110.pool_fees.protocol_fee, + swap_fee: config_v110.pool_fees.swap_fee, + burn_fee: Fee { + share: Decimal::zero(), + }, + }, + feature_toggle: config_v110.feature_toggle, + }; + + CONFIG.save(deps.storage, &config)?; + + #[cw_serde] + struct PairInfoRawV110 { + pub asset_infos: [AssetInfoRaw; 2], + pub contract_addr: CanonicalAddr, + pub liquidity_token: CanonicalAddr, + pub asset_decimals: [u8; 2], + } + + #[cw_serde] + struct PairInfoV110 { + pub asset_infos: [AssetInfo; 2], + pub contract_addr: String, + pub liquidity_token: String, + pub asset_decimals: [u8; 2], + } + + const PAIR_INFO_V110: Item = Item::new("pair_info"); + + // Instantiates the ALL_TIME_BURNED_FEES + let pair_info = PAIR_INFO_V110.load(deps.storage)?; + let asset_info_0 = pair_info.asset_infos[0].to_normal(deps.api)?; + let asset_info_1 = pair_info.asset_infos[1].to_normal(deps.api)?; + + instantiate_fees( + deps.storage, + asset_info_0, + asset_info_1, + ALL_TIME_BURNED_FEES, + )?; + + // migration to v1.2.0 + #[cw_serde] + pub struct PairInfoRawV120 { + pub asset_infos: [AssetInfoRaw; 2], + pub contract_addr: CanonicalAddr, + pub liquidity_token: CanonicalAddr, + pub asset_decimals: [u8; 2], + } + + #[cw_serde] + pub struct PairInfoRawV130 { + pub asset_infos: [AssetInfoRaw; 2], + pub contract_addr: CanonicalAddr, + pub liquidity_token: AssetInfoRaw, + pub asset_decimals: [u8; 2], + pub pair_type: PairType, + } + + const PAIR_INFO_V120: Item = Item::new("pair_info"); + const PAIR_INFO_V130: Item = Item::new("pair_info"); + + let config = PAIR_INFO_V120.load(deps.storage)?; + PAIR_INFO_V130.save( + deps.storage, + &PairInfoRawV130 { + asset_infos: config.asset_infos, + contract_addr: config.contract_addr, + // all liquidity tokens until this version are cw20 tokens + liquidity_token: AssetInfoRaw::Token { + contract_addr: config.liquidity_token, + }, + asset_decimals: config.asset_decimals, + // all pools until this version are ConstantProduct + pair_type: PairType::ConstantProduct, + }, + )?; + + Ok(()) +} diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mock_app.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mock_app.rs new file mode 100644 index 00000000..5829d9b4 --- /dev/null +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mock_app.rs @@ -0,0 +1,14 @@ +use cosmwasm_std::{Addr, Coin}; +use cw_multi_test::{App, AppBuilder, BankKeeper}; + +pub fn mock_app_with_balance(balances: Vec<(Addr, Vec)>) -> App { + let bank = BankKeeper::new(); + + AppBuilder::new() + .with_bank(bank) + .build(|router, _api, storage| { + balances.into_iter().for_each(|(account, amount)| { + router.bank.init_balance(storage, &account, amount).unwrap() + }); + }) +} diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mock_instantiate.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mock_instantiate.rs new file mode 100644 index 00000000..464f6bee --- /dev/null +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mock_instantiate.rs @@ -0,0 +1,53 @@ +use cosmwasm_std::testing::mock_info; +use cosmwasm_std::{Addr, MessageInfo}; +use cw_multi_test::{App, Executor}; + +use white_whale::fee::Fee; +use white_whale::pool_network::asset::{AssetInfo, PairType}; +use white_whale::pool_network::pair::PoolFee; + +use crate::tests::store_code::{store_cw20_token_code, store_pool}; + +pub fn mock_creator() -> MessageInfo { + mock_info("creatorcreator", &[]) +} + +/// Instantiates a pool +pub fn app_mock_instantiate( + app: &mut App, + asset_infos: [AssetInfo; 2], + asset_decimals: [u8; 2], +) -> Addr { + let pool_id = store_pool(app); + let token_id = store_cw20_token_code(app); + + let creator = mock_creator().sender; + + app.instantiate_contract( + pool_id, + creator.clone(), + &white_whale::pool_network::pair::InstantiateMsg { + asset_infos, + token_code_id: token_id, + asset_decimals, + pool_fees: PoolFee { + protocol_fee: Fee { + share: Default::default(), + }, + swap_fee: Fee { + share: Default::default(), + }, + burn_fee: Fee { + share: Default::default(), + }, + }, + fee_collector_addr: creator.to_string(), + pair_type: PairType::ConstantProduct, + token_factory_lp: false, + }, + &[], + "mock pool", + None, + ) + .unwrap() +} diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mod.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mod.rs index da675e98..74f77b92 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mod.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/mod.rs @@ -6,3 +6,10 @@ mod stableswap; mod swap; mod testing; mod withdrawals; + +#[cfg(feature = "injective")] +mod mock_app; +#[cfg(feature = "injective")] +mod mock_instantiate; +#[cfg(feature = "injective")] +mod store_code; diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/provide_liquidity.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/provide_liquidity.rs index 684e0820..1d54b0f1 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/provide_liquidity.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/provide_liquidity.rs @@ -1,5 +1,3 @@ -#[cfg(feature = "token_factory")] -use crate::state::LP_SYMBOL; use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; #[cfg(feature = "token_factory")] use cosmwasm_std::{coin, BankMsg}; @@ -8,8 +6,8 @@ use cosmwasm_std::{ SubMsgResult, Uint128, WasmMsg, }; use cw20::Cw20ExecuteMsg; -use white_whale::fee::Fee; +use white_whale::fee::Fee; #[cfg(feature = "token_factory")] use white_whale::pool_network; use white_whale::pool_network::asset::{Asset, AssetInfo, PairType, MINIMUM_LIQUIDITY_AMOUNT}; @@ -20,6 +18,17 @@ use white_whale::pool_network::pair::{ExecuteMsg, InstantiateMsg, PoolFee}; use crate::contract::{execute, instantiate, reply}; use crate::error::ContractError; +#[cfg(feature = "token_factory")] +use crate::state::LP_SYMBOL; + +#[cfg(feature = "injective")] +use crate::tests::mock_app::mock_app_with_balance; +#[cfg(feature = "injective")] +use crate::tests::mock_instantiate::{app_mock_instantiate, mock_creator}; +#[cfg(feature = "injective")] +use cosmwasm_std::coin; +#[cfg(feature = "injective")] +use cw_multi_test::Executor; #[test] fn provide_liquidity_cw20_lp() { @@ -815,3 +824,86 @@ fn provide_liquidity_tokenfactory_lp() { assert_eq!(bank_send_msg, bank_send_msg_expected); } + +#[cfg(feature = "injective")] +#[test] +fn provide_liquidity_18_decimals() { + let mut app = mock_app_with_balance(vec![( + mock_creator().sender, + vec![ + coin(1_000_000_000_000_000_000000000000000000, "inj"), + coin(1_000_000_000_000_000_000000000000000000, "jni"), + ], + )]); + + let pool_addr = app_mock_instantiate( + &mut app, + [ + AssetInfo::NativeToken { + denom: "inj".to_string(), + }, + AssetInfo::NativeToken { + denom: "jni".to_string(), + }, + ], + [18u8, 18u8], + ); + + //deposit + app.execute_contract( + mock_creator().sender, + pool_addr.clone(), + &ExecuteMsg::ProvideLiquidity { + assets: [ + Asset { + info: AssetInfo::NativeToken { + denom: "inj".to_string(), + }, + amount: Uint128::new(1_000_000_000_000_000000000000000000), + }, + Asset { + info: AssetInfo::NativeToken { + denom: "jni".to_string(), + }, + amount: Uint128::new(1_000_000_000_000_000000000000000000), + }, + ], + slippage_tolerance: None, + receiver: None, + }, + &vec![ + coin(1_000_000_000_000_000000000000000000, "inj"), + coin(1_000_000_000_000_000000000000000000, "jni"), + ], + ) + .unwrap(); + + //deposit again + app.execute_contract( + mock_creator().sender, + pool_addr.clone(), + &ExecuteMsg::ProvideLiquidity { + assets: [ + Asset { + info: AssetInfo::NativeToken { + denom: "inj".to_string(), + }, + amount: Uint128::new(1_000_000_000_000_000000000000000000), + }, + Asset { + info: AssetInfo::NativeToken { + denom: "jni".to_string(), + }, + amount: Uint128::new(1_000_000_000_000_000000000000000000), + }, + ], + slippage_tolerance: None, + receiver: None, + }, + &vec![ + coin(1_000_000_000_000_000000000000000000, "inj"), + coin(1_000_000_000_000_000000000000000000, "jni"), + ], + ) + .unwrap(); +} diff --git a/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/store_code.rs b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/store_code.rs new file mode 100644 index 00000000..62e7b819 --- /dev/null +++ b/contracts/liquidity_hub/pool-network/terraswap_pair/src/tests/store_code.rs @@ -0,0 +1,25 @@ +use cw_multi_test::{App, ContractWrapper}; + +use crate::contract; + +/// Stores the store_pool contract to the app. +pub fn store_pool(app: &mut App) -> u64 { + let contract = Box::new( + ContractWrapper::new(contract::execute, contract::instantiate, contract::query) + .with_migrate(contract::migrate) + .with_reply(contract::reply), + ); + + app.store_code(contract) +} + +/// Stores the base CW20 contract to the app. +pub fn store_cw20_token_code(app: &mut App) -> u64 { + let contract = Box::new(ContractWrapper::new( + cw20_base::contract::execute, + cw20_base::contract::instantiate, + cw20_base::contract::query, + )); + + app.store_code(contract) +} diff --git a/contracts/liquidity_hub/vault-network/vault/Cargo.toml b/contracts/liquidity_hub/vault-network/vault/Cargo.toml index 6c8b24e8..a55ba4b7 100644 --- a/contracts/liquidity_hub/vault-network/vault/Cargo.toml +++ b/contracts/liquidity_hub/vault-network/vault/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vault" -version = "1.2.4" +version = "1.2.6" authors = ["kaimen-sano "] edition.workspace = true description = "Contract to handle a single vault that controls an asset" diff --git a/contracts/liquidity_hub/vault-network/vault/src/contract.rs b/contracts/liquidity_hub/vault-network/vault/src/contract.rs index f17fdc1c..be453023 100644 --- a/contracts/liquidity_hub/vault-network/vault/src/contract.rs +++ b/contracts/liquidity_hub/vault-network/vault/src/contract.rs @@ -23,10 +23,16 @@ use crate::{ }; use crate::execute::receive::withdraw::withdraw; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use cosmwasm_std::CosmosMsg; #[cfg(feature = "token_factory")] use white_whale::pool_network::denom::MsgCreateDenom; +#[cfg(feature = "injective")] +use white_whale::pool_network::denom_injective::MsgCreateDenom; #[cfg(feature = "osmosis_token_factory")] use white_whale::pool_network::denom_osmosis::MsgCreateDenom; @@ -95,7 +101,11 @@ pub fn instantiate( Ok(config) })?; - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] return Ok( response.add_message(>::into(MsgCreateDenom { sender: env.contract.address.to_string(), @@ -219,6 +229,15 @@ pub fn migrate(mut deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result Result Result, VaultError> { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] if is_factory_token(liquidity_asset.as_str()) { let mut messages = vec![]; messages.push(>::into(MsgMint { @@ -180,7 +197,11 @@ fn mint_lp_token_msg( })]) } - #[cfg(all(not(feature = "token_factory"), not(feature = "osmosis_token_factory")))] + #[cfg(all( + not(feature = "token_factory"), + not(feature = "osmosis_token_factory"), + not(feature = "injective") + ))] Ok(vec![CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: liquidity_asset, msg: to_binary(&Cw20ExecuteMsg::Mint { recipient, amount })?, @@ -652,4 +673,51 @@ mod test { // depositor2 is entitled to 3,333 / 18,666 of the total LP supply or 5,000 tokens // depositor3 is entitled to 5,333 / 18,666 of the total LP supply or 8,000 tokens } + + #[cfg(feature = "injective")] + #[test] + fn deposits_handle_18_decimals() { + // simulate an inj vault where users deposit large amounts of inj, even more than the inj supply + let second_depositor = Addr::unchecked("depositor2"); + + let mut app = mock_app_with_balance(vec![ + ( + mock_creator().sender, + coins(1_000_000_000_000000000000000000, "inj"), + ), + ( + second_depositor.clone(), + coins(1_000_000_000_000000000000000000, "inj"), + ), + ]); + + let vault_addr = app_mock_instantiate( + &mut app, + AssetInfo::NativeToken { + denom: "inj".to_string(), + }, + ); + + // first depositor deposits 1_000_000_000_000000000000000000 inj + app.execute_contract( + mock_creator().sender, + vault_addr.clone(), + &white_whale::vault_network::vault::ExecuteMsg::Deposit { + amount: Uint128::new(1_000_000_000_000000000000000000), + }, + &coins(1_000_000_000_000000000000000000, "inj"), + ) + .unwrap(); + + // second depositor deposits 1_000_000_000_000000000000000000 inj + app.execute_contract( + second_depositor.clone(), + vault_addr.clone(), + &white_whale::vault_network::vault::ExecuteMsg::Deposit { + amount: Uint128::new(1_000_000_000_000000000000000000), + }, + &coins(1_000_000_000_000000000000000000, "inj"), + ) + .unwrap(); + } } diff --git a/contracts/liquidity_hub/vault-network/vault/src/execute/receive/mod.rs b/contracts/liquidity_hub/vault-network/vault/src/execute/receive/mod.rs index a973d62b..b08de1b0 100644 --- a/contracts/liquidity_hub/vault-network/vault/src/execute/receive/mod.rs +++ b/contracts/liquidity_hub/vault-network/vault/src/execute/receive/mod.rs @@ -36,7 +36,11 @@ pub fn receive( mod test { use cosmwasm_std::{to_binary, Addr, Uint128}; - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] use cosmwasm_std::testing::mock_info; use white_whale::pool_network::asset::AssetInfo; @@ -93,7 +97,11 @@ mod test { assert_eq!(res.unwrap_err(), VaultError::Unauthorized {}) } - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] #[test] fn cannot_receive_from_not_liquidity_token() { let (mut deps, env) = mock_instantiate( diff --git a/contracts/liquidity_hub/vault-network/vault/src/execute/receive/withdraw.rs b/contracts/liquidity_hub/vault-network/vault/src/execute/receive/withdraw.rs index e1f6f85a..fe5eacc9 100644 --- a/contracts/liquidity_hub/vault-network/vault/src/execute/receive/withdraw.rs +++ b/contracts/liquidity_hub/vault-network/vault/src/execute/receive/withdraw.rs @@ -3,11 +3,17 @@ use cosmwasm_std::{ }; use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg}; -#[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] +#[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" +))] use white_whale::pool_network::asset::is_factory_token; use white_whale::pool_network::asset::{get_total_share, AssetInfo}; #[cfg(feature = "token_factory")] use white_whale::pool_network::denom::{Coin, MsgBurn}; +#[cfg(feature = "injective")] +use white_whale::pool_network::denom_injective::{Coin, MsgBurn}; #[cfg(feature = "osmosis_token_factory")] use white_whale::pool_network::denom_osmosis::{Coin, MsgBurn}; @@ -94,7 +100,11 @@ fn burn_lp_asset_msg( sender: String, amount: Uint128, ) -> Result { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] if is_factory_token(liquidity_asset.as_str()) { Ok(>::into(MsgBurn { sender, @@ -110,7 +120,11 @@ fn burn_lp_asset_msg( funds: vec![], })) } - #[cfg(all(not(feature = "token_factory"), not(feature = "osmosis_token_factory")))] + #[cfg(all( + not(feature = "token_factory"), + not(feature = "osmosis_token_factory"), + not(feature = "injective") + ))] Ok(CosmosMsg::Wasm(WasmMsg::Execute { contract_addr: liquidity_asset, msg: to_binary(&Cw20ExecuteMsg::Burn { amount })?, diff --git a/contracts/liquidity_hub/vault-network/vault/src/migrations.rs b/contracts/liquidity_hub/vault-network/vault/src/migrations.rs index 1dc2212f..1d82a9a0 100644 --- a/contracts/liquidity_hub/vault-network/vault/src/migrations.rs +++ b/contracts/liquidity_hub/vault-network/vault/src/migrations.rs @@ -10,6 +10,7 @@ use white_whale::vault_network::vault::Config; use crate::state::{initialize_fee, ALL_TIME_BURNED_FEES, CONFIG}; +#[cfg(not(feature = "injective"))] pub fn migrate_to_v120(deps: DepsMut) -> Result<(), StdError> { #[cw_serde] pub struct ConfigV113 { @@ -68,6 +69,7 @@ pub fn migrate_to_v120(deps: DepsMut) -> Result<(), StdError> { Ok(()) } +#[cfg(not(feature = "injective"))] pub fn migrate_to_v130(deps: DepsMut) -> Result<(), StdError> { #[cw_serde] pub struct ConfigV120 { @@ -114,3 +116,63 @@ pub fn migrate_to_v130(deps: DepsMut) -> Result<(), StdError> { Ok(()) } + +#[cfg(feature = "injective")] +pub fn migrate_to_v126(deps: DepsMut) -> Result<(), StdError> { + #[cw_serde] + pub struct ConfigV113 { + /// The owner of the vault + pub owner: Addr, + /// The asset info the vault manages + pub asset_info: AssetInfo, + /// If flash-loans are enabled + pub flash_loan_enabled: bool, + /// If deposits are enabled + pub deposit_enabled: bool, + /// If withdrawals are enabled + pub withdraw_enabled: bool, + /// The address of the liquidity token + pub liquidity_token: Addr, + /// The address of the fee collector + pub fee_collector_addr: Addr, + /// The fees associated with this vault + pub fees: VaultFeeV113, + } + + #[cw_serde] + pub struct VaultFeeV113 { + pub protocol_fee: Fee, + pub flash_loan_fee: Fee, + } + + const CONFIG_V113: Item = Item::new("config"); + + let config_v113 = CONFIG_V113.load(deps.storage)?; + + // Add burn fee to config. Zero fee is used as default. + let config = Config { + owner: config_v113.owner, + asset_info: config_v113.asset_info, + flash_loan_enabled: config_v113.flash_loan_enabled, + deposit_enabled: config_v113.deposit_enabled, + withdraw_enabled: config_v113.withdraw_enabled, + lp_asset: AssetInfo::Token { + contract_addr: config_v113.liquidity_token.to_string(), + }, + fee_collector_addr: config_v113.fee_collector_addr, + fees: VaultFee { + protocol_fee: config_v113.fees.protocol_fee, + flash_loan_fee: config_v113.fees.flash_loan_fee, + burn_fee: Fee { + share: Decimal::zero(), + }, + }, + }; + + CONFIG.save(deps.storage, &config)?; + + // initialize the burned fee storage item + initialize_fee(deps.storage, ALL_TIME_BURNED_FEES, config.asset_info)?; + + Ok(()) +} diff --git a/contracts/liquidity_hub/vault-network/vault_router/Cargo.toml b/contracts/liquidity_hub/vault-network/vault_router/Cargo.toml index 1bb5f8df..e6939bfe 100644 --- a/contracts/liquidity_hub/vault-network/vault_router/Cargo.toml +++ b/contracts/liquidity_hub/vault-network/vault_router/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "vault_router" -version = "1.1.5" +version = "1.1.6" authors = [ "kaimen-sano , Kerber0x ", ] diff --git a/contracts/liquidity_hub/vault-network/vault_router/src/execute/complete_loan.rs b/contracts/liquidity_hub/vault-network/vault_router/src/execute/complete_loan.rs index 40154f8a..9f5586b6 100644 --- a/contracts/liquidity_hub/vault-network/vault_router/src/execute/complete_loan.rs +++ b/contracts/liquidity_hub/vault-network/vault_router/src/execute/complete_loan.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{ - coins, to_binary, Addr, BankMsg, CosmosMsg, DepsMut, Env, MessageInfo, Response, WasmMsg, + attr, coins, to_binary, Addr, BankMsg, CosmosMsg, DepsMut, Env, MessageInfo, Response, WasmMsg, }; use white_whale::pool_network::asset::{Asset, AssetInfo}; use white_whale::vault_network::vault::PaybackAmountResponse; @@ -18,6 +18,8 @@ pub fn complete_loan( return Err(VaultRouterError::Unauthorized {}); } + let mut attributes = vec![]; + // pay back loans and profit let messages: Vec> = assets .into_iter() @@ -58,6 +60,12 @@ pub fn complete_loan( required_amount: payback_amount.payback_amount, })?; + attributes.push(attr( + "payback_amount", + payback_amount.payback_amount.to_string(), + )); + attributes.push(attr("profit_amount", profit_amount.to_string())); + let mut response_messages: Vec = vec![]; let payback_loan_msg: StdResult = match loaned_asset.info.clone() { AssetInfo::NativeToken { denom } => Ok(BankMsg::Send { @@ -106,7 +114,8 @@ pub fn complete_loan( Ok(Response::new() .add_messages(messages.concat()) - .add_attributes(vec![("method", "complete_loan")])) + .add_attributes(vec![("method", "complete_loan")]) + .add_attributes(attributes)) } #[cfg(test)] diff --git a/contracts/liquidity_hub/vault-network/vault_router/src/execute/flash_loan.rs b/contracts/liquidity_hub/vault-network/vault_router/src/execute/flash_loan.rs index a394eaa7..db3f84f5 100644 --- a/contracts/liquidity_hub/vault-network/vault_router/src/execute/flash_loan.rs +++ b/contracts/liquidity_hub/vault-network/vault_router/src/execute/flash_loan.rs @@ -398,6 +398,14 @@ mod tests { key: "method".to_string(), value: "complete_loan".to_string(), }, + Attribute { + key: "payback_amount".to_string(), + value: "1066".to_string(), + }, + Attribute { + key: "profit_amount".to_string(), + value: "0".to_string(), + }, ]), Event::new("transfer").add_attributes(vec![ Attribute { diff --git a/packages/white-whale/Cargo.toml b/packages/white-whale/Cargo.toml index 66f56ab3..0d76e866 100644 --- a/packages/white-whale/Cargo.toml +++ b/packages/white-whale/Cargo.toml @@ -15,7 +15,7 @@ documentation = "https://whitewhale.money" # for quicker tests, cargo test --lib # for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] -injective = [] +injective = ["token_factory"] token_factory = ["cosmwasm-std/stargate", "cosmwasm-std/cosmwasm_1_1"] osmosis_token_factory = ["token_factory"] # this is for the osmosis token factory proto definitions, which defer from the standard token factory :) diff --git a/packages/white-whale/src/pool_network/asset.rs b/packages/white-whale/src/pool_network/asset.rs index 04176e44..e3823c93 100644 --- a/packages/white-whale/src/pool_network/asset.rs +++ b/packages/white-whale/src/pool_network/asset.rs @@ -620,7 +620,11 @@ impl TrioInfoRaw { /// Gets the total supply of the given liquidity asset pub fn get_total_share(deps: &Deps, liquidity_asset: String) -> StdResult { - #[cfg(any(feature = "token_factory", feature = "osmosis_token_factory"))] + #[cfg(any( + feature = "token_factory", + feature = "osmosis_token_factory", + feature = "injective" + ))] let total_share = if is_factory_token(liquidity_asset.as_str()) { //bank query total deps.querier.query_supply(&liquidity_asset)?.amount @@ -631,7 +635,11 @@ pub fn get_total_share(deps: &Deps, liquidity_asset: String) -> StdResult. The resulting denom's admin is +/// originally set to be the creator, but this can be changed later. The token +/// denom does not indicate the current admin. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgCreateDenom")] +pub struct MsgCreateDenom { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + /// subdenom can be up to 44 "alphanumeric" characters long. + #[prost(string, tag = "2")] + pub subdenom: ::prost::alloc::string::String, +} + +/// MsgCreateDenomResponse is the return value of MsgCreateDenom +/// It returns the full string of the newly created denom +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgCreateDenomResponse")] +pub struct MsgCreateDenomResponse { + #[prost(string, tag = "1")] + pub new_token_denom: ::prost::alloc::string::String, +} + +/// MsgMint is the sdk.Msg type for allowing an admin account to mint +/// more of a token. For now, we only support minting to the sender account +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgMint")] +pub struct MsgMint { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, +} + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgMintResponse")] +pub struct MsgMintResponse {} + +/// MsgBurn is the sdk.Msg type for allowing an admin account to burn +/// a token. For now, we only support burning from the sender account. +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgBurn")] +pub struct MsgBurn { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, +} + +#[derive( + Clone, + PartialEq, + Eq, + ::prost::Message, + serde::Serialize, + serde::Deserialize, + schemars::JsonSchema, + CosmwasmExt, +)] +#[proto_message(type_url = "/injective.tokenfactory.v1beta1.MsgBurnResponse")] +pub struct MsgBurnResponse {} diff --git a/packages/white-whale/src/pool_network/mod.rs b/packages/white-whale/src/pool_network/mod.rs index 52c2d7fc..e86c6456 100644 --- a/packages/white-whale/src/pool_network/mod.rs +++ b/packages/white-whale/src/pool_network/mod.rs @@ -1,6 +1,8 @@ pub mod asset; #[cfg(feature = "token_factory")] pub mod denom; +#[cfg(feature = "injective")] +pub mod denom_injective; #[cfg(feature = "osmosis_token_factory")] pub mod denom_osmosis; pub mod factory; diff --git a/packages/white-whale/src/whale_lair.rs b/packages/white-whale/src/whale_lair.rs index 334dbf84..fa52328c 100644 --- a/packages/white-whale/src/whale_lair.rs +++ b/packages/white-whale/src/whale_lair.rs @@ -32,7 +32,7 @@ impl Default for Bond { Self { asset: Asset { info: AssetInfo::NativeToken { - denom: "".to_string(), + denom: String::new(), }, amount: Uint128::zero(), }, diff --git a/scripts/deployment/deploy_env/base_injective.env b/scripts/deployment/deploy_env/base_injective.env index e5a04ef5..de2b469b 100644 --- a/scripts/deployment/deploy_env/base_injective.env +++ b/scripts/deployment/deploy_env/base_injective.env @@ -1,6 +1,6 @@ if [ -n "$ZSH_VERSION" ]; then # Using an array for TXFLAG - TXFLAG=(--node $RPC --chain-id $CHAIN_ID --gas-prices=500000000inj --gas 10000000 -y -b block --output json) + TXFLAG=(--node $RPC --chain-id $CHAIN_ID --gas-prices=500000000inj --gas 10000000 -y -b sync --output json) else # Using a string for TXFLAG TXFLAG="--node $RPC --chain-id $CHAIN_ID --gas-prices=500000000inj --gas 10000000 -y -b block --output json"