From ff81f7b0075a2a68e9790fa45fca01f5439f0b62 Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Fri, 16 Jun 2023 14:34:21 +0700 Subject: [PATCH 1/5] expose setting custom subdenom on instantiate --- contracts/transmuter/src/contract.rs | 3 +- .../src/test/cases/comprehensive_flows.rs | 161 +++++++++++------- .../src/test/cases/units/join_and_exit.rs | 5 + .../src/test/cases/units/spot_price.rs | 1 + .../src/test/cases/units/swap/mod.rs | 1 + 5 files changed, 107 insertions(+), 64 deletions(-) diff --git a/contracts/transmuter/src/contract.rs b/contracts/transmuter/src/contract.rs index b55de45..fa8bdd9 100644 --- a/contracts/transmuter/src/contract.rs +++ b/contracts/transmuter/src/contract.rs @@ -42,6 +42,7 @@ impl Transmuter<'_> { &self, ctx: (DepsMut, Env, MessageInfo), pool_asset_denoms: Vec, + lp_subdenom: String, ) -> Result { let (deps, env, _info) = ctx; @@ -59,7 +60,7 @@ impl Transmuter<'_> { let msg_create_lp_denom = SubMsg::reply_on_success( MsgCreateDenom { sender: env.contract.address.to_string(), - subdenom: "transmuter/poolshare".to_owned(), + subdenom: lp_subdenom, }, CREATE_LP_DENOM_REPLY_ID, ); diff --git a/contracts/transmuter/src/test/cases/comprehensive_flows.rs b/contracts/transmuter/src/test/cases/comprehensive_flows.rs index f0c61f7..d30a602 100644 --- a/contracts/transmuter/src/test/cases/comprehensive_flows.rs +++ b/contracts/transmuter/src/test/cases/comprehensive_flows.rs @@ -18,10 +18,13 @@ use osmosis_test_tube::{ cosmrs::proto::cosmos::bank::v1beta1::MsgSend, Account, Bank, Module, OsmosisTestApp, }; -const ETH_USDC: &str = "ibc/AXLETHUSDC"; -const ETH_DAI: &str = "ibc/AXLETHDAI"; +const AXL_USDC: &str = "ibc/AXLETHUSDC"; +const AXL_DAI: &str = "ibc/AXLETHDAI"; const COSMOS_USDC: &str = "ibc/COSMOSUSDC"; +const AXL_ETH: &str = "ibc/AXLETH"; +const WH_ETH: &str = "ibc/WHETH"; + #[test] fn test_join_pool() { let app = OsmosisTestApp::new(); @@ -30,16 +33,17 @@ fn test_join_pool() { "provider_1", vec![ Coin::new(2_000, COSMOS_USDC), - Coin::new(2_000, ETH_USDC), + Coin::new(2_000, AXL_USDC), Coin::new(2_000, "urandom"), ], ) .with_account( "provider_2", - vec![Coin::new(2_000, COSMOS_USDC), Coin::new(2_000, ETH_USDC)], + vec![Coin::new(2_000, COSMOS_USDC), Coin::new(2_000, AXL_USDC)], ) .with_instantiate_msg(InstantiateMsg { - pool_asset_denoms: vec![ETH_USDC.to_string(), COSMOS_USDC.to_string()], + pool_asset_denoms: vec![AXL_USDC.to_string(), COSMOS_USDC.to_string()], + lp_subdenom: "transmuter/poolshare".to_string(), }) .build(&app); @@ -61,7 +65,7 @@ fn test_join_pool() { assert_contract_err( ContractError::InvalidJoinPoolDenom { denom: "urandom".to_string(), - expected_denom: vec![ETH_USDC.to_string(), COSMOS_USDC.to_string()], + expected_denom: vec![AXL_USDC.to_string(), COSMOS_USDC.to_string()], }, err, ); @@ -86,7 +90,7 @@ fn test_join_pool() { assert_eq!( total_pool_liquidity, - vec![Coin::new(0, ETH_USDC), tokens_in[0].clone()] + vec![Coin::new(0, AXL_USDC), tokens_in[0].clone()] ); // check shares @@ -106,13 +110,13 @@ fn test_join_pool() { assert_eq!(total_shares, tokens_in[0].amount); // join pool with multiple correct pool's denom should added to the contract's balance and update state - let tokens_in = vec![Coin::new(1_000, ETH_USDC), Coin::new(1_000, COSMOS_USDC)]; + let tokens_in = vec![Coin::new(1_000, AXL_USDC), Coin::new(1_000, COSMOS_USDC)]; t.contract .execute(&ExecMsg::JoinPool {}, &tokens_in, &t.accounts["provider_1"]) .unwrap(); // check contract balances - t.assert_contract_balances(&[Coin::new(1_000, ETH_USDC), Coin::new(2_000, COSMOS_USDC)]); + t.assert_contract_balances(&[Coin::new(1_000, AXL_USDC), Coin::new(2_000, COSMOS_USDC)]); // check pool balance let GetTotalPoolLiquidityResponse { @@ -124,7 +128,7 @@ fn test_join_pool() { assert_eq!( total_pool_liquidity, - vec![Coin::new(1_000, ETH_USDC), Coin::new(2_000, COSMOS_USDC)] + vec![Coin::new(1_000, AXL_USDC), Coin::new(2_000, COSMOS_USDC)] ); // check shares @@ -144,13 +148,13 @@ fn test_join_pool() { assert_eq!(total_shares, Uint128::new(3000)); // join pool with another provider with multiple correct pool's denom should added to the contract's balance and update state - let tokens_in = vec![Coin::new(2_000, ETH_USDC), Coin::new(2_000, COSMOS_USDC)]; + let tokens_in = vec![Coin::new(2_000, AXL_USDC), Coin::new(2_000, COSMOS_USDC)]; t.contract .execute(&ExecMsg::JoinPool {}, &tokens_in, &t.accounts["provider_2"]) .unwrap(); // check contract balances - t.assert_contract_balances(&[Coin::new(3_000, ETH_USDC), Coin::new(4_000, COSMOS_USDC)]); + t.assert_contract_balances(&[Coin::new(3_000, AXL_USDC), Coin::new(4_000, COSMOS_USDC)]); // check pool balance let GetTotalPoolLiquidityResponse { @@ -162,7 +166,7 @@ fn test_join_pool() { assert_eq!( total_pool_liquidity, - vec![Coin::new(3_000, ETH_USDC), Coin::new(4_000, COSMOS_USDC)] + vec![Coin::new(3_000, AXL_USDC), Coin::new(4_000, COSMOS_USDC)] ); // check shares @@ -192,15 +196,16 @@ fn test_swap() { .with_account( "alice", vec![ - Coin::new(1_500, ETH_USDC), + Coin::new(1_500, AXL_USDC), Coin::new(1_000, COSMOS_USDC), Coin::new(1_000, "urandom2"), ], ) - .with_account("bob", vec![Coin::new(29_902, ETH_USDC)]) + .with_account("bob", vec![Coin::new(29_902, AXL_USDC)]) .with_account("provider", vec![Coin::new(200_000, COSMOS_USDC)]) .with_instantiate_msg(InstantiateMsg { - pool_asset_denoms: vec![ETH_USDC.to_string(), COSMOS_USDC.to_string()], + pool_asset_denoms: vec![AXL_USDC.to_string(), COSMOS_USDC.to_string()], + lp_subdenom: "transmuter/poolshare".to_string(), }) .build(&app); @@ -222,7 +227,7 @@ fn test_swap() { .swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["alice"].address(), - token_in: Some(Coin::new(1_500, ETH_USDC).into()), + token_in: Some(Coin::new(1_500, AXL_USDC).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: COSMOS_USDC.to_string(), @@ -265,7 +270,7 @@ fn test_swap() { assert_contract_err( ContractError::InvalidTransmuteDenom { denom: "urandom".to_string(), - expected_denom: vec![ETH_USDC.to_string(), COSMOS_USDC.to_string()], + expected_denom: vec![AXL_USDC.to_string(), COSMOS_USDC.to_string()], }, err, ); @@ -288,13 +293,13 @@ fn test_swap() { assert_contract_err( ContractError::InvalidTransmuteDenom { denom: "urandom2".to_string(), - expected_denom: vec![ETH_USDC.to_string(), COSMOS_USDC.to_string()], + expected_denom: vec![AXL_USDC.to_string(), COSMOS_USDC.to_string()], }, err, ); // swap with correct token_in should succeed this time - let token_in = Coin::new(1_500, ETH_USDC); + let token_in = Coin::new(1_500, AXL_USDC); cp.swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["alice"].address(), @@ -311,7 +316,7 @@ fn test_swap() { // check balances t.assert_contract_balances(&[ - Coin::new(1_500, ETH_USDC), + Coin::new(1_500, AXL_USDC), Coin::new(100_000 + 100_000 - 1_500, COSMOS_USDC), // +100_000 due to bank send ]); @@ -325,7 +330,7 @@ fn test_swap() { assert_eq!( total_pool_liquidity, vec![ - Coin::new(1_500, ETH_USDC), + Coin::new(1_500, AXL_USDC), Coin::new(100_000 - 1_500, COSMOS_USDC) ] ); @@ -342,7 +347,7 @@ fn test_swap() { // swap again with another user // swap with correct token_in should succeed this time - let token_in = Coin::new(29_902, ETH_USDC); + let token_in = Coin::new(29_902, AXL_USDC); cp.swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["bob"].address(), @@ -359,7 +364,7 @@ fn test_swap() { // check balances t.assert_contract_balances(&[ - Coin::new(1_500 + 29_902, ETH_USDC), + Coin::new(1_500 + 29_902, AXL_USDC), Coin::new(100_000 + 100_000 - 1_500 - 29_902, COSMOS_USDC), // +100_000 due to bank send ]); @@ -373,7 +378,7 @@ fn test_swap() { assert_eq!( total_pool_liquidity, vec![ - Coin::new(1_500 + 29_902, ETH_USDC), + Coin::new(1_500 + 29_902, AXL_USDC), Coin::new(100_000 - 1_500 - 29_902, COSMOS_USDC) ] ); @@ -387,11 +392,12 @@ fn test_exit_pool() { let cp = CosmwasmPool::new(&app); let t = TestEnvBuilder::new() - .with_account("user", vec![Coin::new(1_500, ETH_USDC)]) + .with_account("user", vec![Coin::new(1_500, AXL_USDC)]) .with_account("provider_1", vec![Coin::new(100_000, COSMOS_USDC)]) .with_account("provider_2", vec![Coin::new(100_000, COSMOS_USDC)]) .with_instantiate_msg(InstantiateMsg { - pool_asset_denoms: vec![ETH_USDC.to_string(), COSMOS_USDC.to_string()], + pool_asset_denoms: vec![AXL_USDC.to_string(), COSMOS_USDC.to_string()], + lp_subdenom: "transmuter/poolshare".to_string(), }) .build(&app); @@ -413,7 +419,7 @@ fn test_exit_pool() { .unwrap(); // swap to build up token_in - let token_in = Coin::new(1_500, ETH_USDC); + let token_in = Coin::new(1_500, AXL_USDC); cp.swap_exact_amount_in( MsgSwapExactAmountIn { @@ -434,7 +440,7 @@ fn test_exit_pool() { .contract .execute( &ExecMsg::ExitPool { - tokens_out: vec![Coin::new(1_500, ETH_USDC)], + tokens_out: vec![Coin::new(1_500, AXL_USDC)], }, &[], &t.accounts["user"], @@ -453,7 +459,7 @@ fn test_exit_pool() { t.contract .execute( &ExecMsg::ExitPool { - tokens_out: vec![Coin::new(500, ETH_USDC)], + tokens_out: vec![Coin::new(500, AXL_USDC)], }, &[], &t.accounts["provider_1"], @@ -478,7 +484,7 @@ fn test_exit_pool() { // check balances t.assert_contract_balances(&[ - Coin::new(1500 - 500, ETH_USDC), + Coin::new(1500 - 500, AXL_USDC), Coin::new(200_000 - 1500, COSMOS_USDC), ]); @@ -492,7 +498,7 @@ fn test_exit_pool() { assert_eq!( total_pool_liquidity, vec![ - Coin::new(1500 - 500, ETH_USDC), + Coin::new(1500 - 500, AXL_USDC), Coin::new(200_000 - 1500, COSMOS_USDC) ] ); @@ -501,7 +507,7 @@ fn test_exit_pool() { t.contract .execute( &ExecMsg::ExitPool { - tokens_out: vec![Coin::new(1_000, ETH_USDC), Coin::new(99_000, COSMOS_USDC)], + tokens_out: vec![Coin::new(1_000, AXL_USDC), Coin::new(99_000, COSMOS_USDC)], }, &[], &t.accounts["provider_2"], @@ -537,7 +543,7 @@ fn test_exit_pool() { assert_eq!( total_pool_liquidity, vec![ - Coin::new(0, ETH_USDC), + Coin::new(0, AXL_USDC), Coin::new(200_000 - 1500 - 99_000, COSMOS_USDC) ] ); @@ -547,7 +553,7 @@ fn test_exit_pool() { .contract .execute( &ExecMsg::ExitPool { - tokens_out: vec![Coin::new(1, ETH_USDC)], + tokens_out: vec![Coin::new(1, AXL_USDC)], }, &[], &t.accounts["provider_2"], @@ -567,7 +573,7 @@ fn test_exit_pool() { .contract .execute( &ExecMsg::ExitPool { - tokens_out: vec![Coin::new(1, ETH_USDC)], + tokens_out: vec![Coin::new(1, AXL_USDC)], }, &[], &t.accounts["provider_1"], @@ -576,8 +582,8 @@ fn test_exit_pool() { assert_contract_err( ContractError::InsufficientPoolAsset { - required: Coin::new(1, ETH_USDC), - available: Coin::new(0, ETH_USDC), + required: Coin::new(1, AXL_USDC), + available: Coin::new(0, AXL_USDC), }, err, ); @@ -589,15 +595,16 @@ fn test_3_pool_swap() { let cp = CosmwasmPool::new(&app); let t = TestEnvBuilder::new() - .with_account("alice", vec![Coin::new(1_500, ETH_USDC)]) - .with_account("bob", vec![Coin::new(1_500, ETH_DAI)]) + .with_account("alice", vec![Coin::new(1_500, AXL_USDC)]) + .with_account("bob", vec![Coin::new(1_500, AXL_DAI)]) .with_account("provider", vec![Coin::new(100_000, COSMOS_USDC)]) .with_instantiate_msg(InstantiateMsg { pool_asset_denoms: vec![ - ETH_USDC.to_string(), - ETH_DAI.to_string(), + AXL_USDC.to_string(), + AXL_DAI.to_string(), COSMOS_USDC.to_string(), ], + lp_subdenom: "transmuter/poolshare".to_string(), }) .build(&app); @@ -641,8 +648,8 @@ fn test_3_pool_swap() { assert_eq!( total_pool_liquidity, vec![ - Coin::new(0, ETH_USDC), - Coin::new(0, ETH_DAI), + Coin::new(0, AXL_USDC), + Coin::new(0, AXL_DAI), Coin::new(100_000, COSMOS_USDC) ] ); @@ -653,10 +660,10 @@ fn test_3_pool_swap() { .swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["alice"].address(), - token_in: Some(Coin::new(1_000, ETH_USDC).into()), + token_in: Some(Coin::new(1_000, AXL_USDC).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, - token_out_denom: ETH_DAI.to_string(), + token_out_denom: AXL_DAI.to_string(), }], token_out_min_amount: Uint128::from(1_000u128).to_string(), }, @@ -666,8 +673,8 @@ fn test_3_pool_swap() { assert_contract_err( ContractError::InsufficientPoolAsset { - required: Coin::new(1_000, ETH_DAI), - available: Coin::new(0, ETH_DAI), + required: Coin::new(1_000, AXL_DAI), + available: Coin::new(0, AXL_DAI), }, err, ); @@ -676,7 +683,7 @@ fn test_3_pool_swap() { cp.swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["alice"].address(), - token_in: Some(Coin::new(1_000, ETH_USDC).into()), + token_in: Some(Coin::new(1_000, AXL_USDC).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, token_out_denom: COSMOS_USDC.to_string(), @@ -688,12 +695,12 @@ fn test_3_pool_swap() { .unwrap(); // check contract balances - t.assert_contract_balances(&[Coin::new(1_000, ETH_USDC), Coin::new(99_000, COSMOS_USDC)]); + t.assert_contract_balances(&[Coin::new(1_000, AXL_USDC), Coin::new(99_000, COSMOS_USDC)]); // check alice balance t.assert_account_balances( "alice", - vec![Coin::new(500, ETH_USDC), Coin::new(1_000, COSMOS_USDC)], + vec![Coin::new(500, AXL_USDC), Coin::new(1_000, COSMOS_USDC)], vec!["uosmo", "ucosm"], ); @@ -708,8 +715,8 @@ fn test_3_pool_swap() { assert_eq!( total_pool_liquidity, vec![ - Coin::new(1_000, ETH_USDC), - Coin::new(0, ETH_DAI), + Coin::new(1_000, AXL_USDC), + Coin::new(0, AXL_DAI), Coin::new(99_000, COSMOS_USDC) ] ); @@ -719,10 +726,10 @@ fn test_3_pool_swap() { cp.swap_exact_amount_in( MsgSwapExactAmountIn { sender: t.accounts["bob"].address(), - token_in: Some(Coin::new(1_000, ETH_DAI).into()), + token_in: Some(Coin::new(1_000, AXL_DAI).into()), routes: vec![SwapAmountInRoute { pool_id: t.contract.pool_id, - token_out_denom: ETH_USDC.to_string(), + token_out_denom: AXL_USDC.to_string(), }], token_out_min_amount: Uint128::from(1_000u128).to_string(), }, @@ -731,12 +738,12 @@ fn test_3_pool_swap() { .unwrap(); // check contract balances - t.assert_contract_balances(&[Coin::new(1_000, ETH_DAI), Coin::new(99_000, COSMOS_USDC)]); + t.assert_contract_balances(&[Coin::new(1_000, AXL_DAI), Coin::new(99_000, COSMOS_USDC)]); // check bob balances t.assert_account_balances( "bob", - vec![Coin::new(500, ETH_DAI), Coin::new(1_000, ETH_USDC)], + vec![Coin::new(500, AXL_DAI), Coin::new(1_000, AXL_USDC)], vec!["uosmo"], ); @@ -751,8 +758,8 @@ fn test_3_pool_swap() { assert_eq!( total_pool_liquidity, vec![ - Coin::new(0, ETH_USDC), - Coin::new(1_000, ETH_DAI), + Coin::new(0, AXL_USDC), + Coin::new(1_000, AXL_DAI), Coin::new(99_000, COSMOS_USDC) ] ); @@ -761,7 +768,7 @@ fn test_3_pool_swap() { t.contract .execute( &ExecMsg::ExitPool { - tokens_out: vec![Coin::new(1_000, ETH_DAI), Coin::new(99_000, COSMOS_USDC)], + tokens_out: vec![Coin::new(1_000, AXL_DAI), Coin::new(99_000, COSMOS_USDC)], }, &[], &t.accounts["provider"], @@ -782,8 +789,8 @@ fn test_3_pool_swap() { assert_eq!( total_pool_liquidity, vec![ - Coin::new(0, ETH_USDC), - Coin::new(0, ETH_DAI), + Coin::new(0, AXL_USDC), + Coin::new(0, AXL_DAI), Coin::new(0, COSMOS_USDC) ] ); @@ -791,7 +798,35 @@ fn test_3_pool_swap() { t.assert_contract_balances(&[]); t.assert_account_balances( "provider", - vec![Coin::new(1_000, ETH_DAI), Coin::new(99_000, COSMOS_USDC)], + vec![Coin::new(1_000, AXL_DAI), Coin::new(99_000, COSMOS_USDC)], vec!["uosmo"], ); } + +#[test] +fn test_swap_lp_denom() { + let app = OsmosisTestApp::new(); + + let lp_subdenom = "transmuter/eth"; + let t = TestEnvBuilder::new() + .with_account("alice", vec![Coin::new(1_500, AXL_ETH)]) + .with_account("bob", vec![Coin::new(1_500, WH_ETH)]) + .with_account( + "provider", + vec![Coin::new(100_000, AXL_ETH), Coin::new(100_000, WH_ETH)], + ) + .with_instantiate_msg(InstantiateMsg { + pool_asset_denoms: vec![AXL_ETH.to_string(), WH_ETH.to_string()], + lp_subdenom: lp_subdenom.to_string(), + }) + .build(&app); + + // pool share denom + let GetShareDenomResponse { share_denom } = + t.contract.query(&QueryMsg::GetShareDenom {}).unwrap(); + + assert_eq!( + format!("factory/{}/{}", t.contract.contract_addr, lp_subdenom), + share_denom + ); +} diff --git a/contracts/transmuter/src/test/cases/units/join_and_exit.rs b/contracts/transmuter/src/test/cases/units/join_and_exit.rs index e5146c1..91aeaeb 100644 --- a/contracts/transmuter/src/test/cases/units/join_and_exit.rs +++ b/contracts/transmuter/src/test/cases/units/join_and_exit.rs @@ -64,6 +64,7 @@ fn test_join_pool_with_single_lp_should_update_shares_and_liquidity_properly() { .with_account("provider", case.funds.clone()) .with_instantiate_msg(crate::contract::InstantiateMsg { pool_asset_denoms: vec!["denoma".to_string(), "denomb".to_string()], + lp_subdenom: "transmuter/poolshare".to_string(), }) .build(&app); @@ -177,6 +178,7 @@ fn test_join_pool_should_update_shares_and_liquidity_properly() { let t = builder .with_instantiate_msg(InstantiateMsg { pool_asset_denoms: vec!["denoma".to_string(), "denomb".to_string()], + lp_subdenom: "transmuter/poolshare".to_string(), }) .build(&app); @@ -288,6 +290,7 @@ fn test_exit_pool_less_than_their_shares_should_update_shares_and_liquidity_prop .with_account("addr1", case.join.clone()) .with_instantiate_msg(InstantiateMsg { pool_asset_denoms: vec!["denoma".to_string(), "denomb".to_string()], + lp_subdenom: "transmuter/poolshare".to_string(), }) .build(&app); @@ -478,6 +481,7 @@ fn test_exit_pool_greater_than_their_shares_should_fail() { .with_account("addr", case.join.clone()) .with_instantiate_msg(InstantiateMsg { pool_asset_denoms: vec!["denoma".to_string(), "denomb".to_string()], + lp_subdenom: "transmuter/poolshare".to_string(), }) .build(&app); @@ -523,6 +527,7 @@ fn test_exit_pool_within_shares_but_over_joined_denom_amount() { .with_account("addr1", vec![Coin::new(200_000_000, "denomb")]) .with_instantiate_msg(InstantiateMsg { pool_asset_denoms: vec!["denoma".to_string(), "denomb".to_string()], + lp_subdenom: "transmuter/poolshare".to_string(), }) .build(&app); diff --git a/contracts/transmuter/src/test/cases/units/spot_price.rs b/contracts/transmuter/src/test/cases/units/spot_price.rs index a579ee2..5437083 100644 --- a/contracts/transmuter/src/test/cases/units/spot_price.rs +++ b/contracts/transmuter/src/test/cases/units/spot_price.rs @@ -25,6 +25,7 @@ fn test_spot_price(liquidity: &[Coin]) { .instantiate( (deps.as_mut(), mock_env(), mock_info("creator", &[])), vec!["denom0".to_string(), "denom1".to_string()], + "transmuter/poolshare".to_string(), ) .unwrap(); diff --git a/contracts/transmuter/src/test/cases/units/swap/mod.rs b/contracts/transmuter/src/test/cases/units/swap/mod.rs index 03778e8..3181b1e 100644 --- a/contracts/transmuter/src/test/cases/units/swap/mod.rs +++ b/contracts/transmuter/src/test/cases/units/swap/mod.rs @@ -247,6 +247,7 @@ fn pool_with_single_lp(app: &'_ OsmosisTestApp, pool_assets: Vec) -> TestE ) .with_instantiate_msg(crate::contract::InstantiateMsg { pool_asset_denoms: pool_assets.iter().map(|c| c.denom.clone()).collect(), + lp_subdenom: "transmuter/poolshare".to_string(), }) .build(app); From b70cc0637f8aebe2fb45033aa6f469e22befbcd0 Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Fri, 16 Jun 2023 15:26:26 +0700 Subject: [PATCH 2/5] rename comprehensive flows to scenarios --- contracts/transmuter/src/test/cases/mod.rs | 2 +- .../src/test/cases/{comprehensive_flows.rs => scenarios.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename contracts/transmuter/src/test/cases/{comprehensive_flows.rs => scenarios.rs} (100%) diff --git a/contracts/transmuter/src/test/cases/mod.rs b/contracts/transmuter/src/test/cases/mod.rs index d92edcc..b39b7ff 100644 --- a/contracts/transmuter/src/test/cases/mod.rs +++ b/contracts/transmuter/src/test/cases/mod.rs @@ -1,4 +1,4 @@ -mod comprehensive_flows; +mod scenarios; #[macro_use] mod units; diff --git a/contracts/transmuter/src/test/cases/comprehensive_flows.rs b/contracts/transmuter/src/test/cases/scenarios.rs similarity index 100% rename from contracts/transmuter/src/test/cases/comprehensive_flows.rs rename to contracts/transmuter/src/test/cases/scenarios.rs From 06e28e58e924a59e216e3eea6e4f013d624cc692 Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Fri, 16 Jun 2023 15:50:33 +0700 Subject: [PATCH 3/5] implement admin --- contracts/transmuter/src/contract.rs | 35 +++++++++++++++++-- contracts/transmuter/src/error.rs | 4 +++ contracts/transmuter/src/shares.rs | 6 ++-- .../transmuter/src/test/cases/scenarios.rs | 5 +++ .../src/test/cases/units/join_and_exit.rs | 5 +++ .../src/test/cases/units/spot_price.rs | 1 + .../src/test/cases/units/swap/mod.rs | 1 + 7 files changed, 52 insertions(+), 5 deletions(-) diff --git a/contracts/transmuter/src/contract.rs b/contracts/transmuter/src/contract.rs index fa8bdd9..d283b1e 100644 --- a/contracts/transmuter/src/contract.rs +++ b/contracts/transmuter/src/contract.rs @@ -1,8 +1,9 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ ensure, ensure_eq, BankMsg, Coin, Decimal, Deps, DepsMut, Env, MessageInfo, Reply, Response, - StdError, SubMsg, Uint128, + StdError, StdResult, SubMsg, Uint128, }; +use cw_controllers::{Admin, AdminResponse}; use cw_storage_plus::Item; use osmosis_std::types::osmosis::tokenfactory::v1beta1::{ MsgBurn, MsgCreateDenom, MsgCreateDenomResponse, MsgMint, @@ -23,6 +24,7 @@ pub struct Transmuter<'a> { pub(crate) active_status: Item<'a, bool>, pub(crate) pool: Item<'a, TransmuterPool>, pub(crate) shares: Shares<'a>, + pub(crate) admin: Admin<'a>, } #[contract] @@ -32,7 +34,8 @@ impl Transmuter<'_> { Self { active_status: Item::new("active_status"), pool: Item::new("pool"), - shares: Shares::new(), + shares: Shares::new("shares"), + admin: Admin::new("admin"), } } @@ -43,6 +46,7 @@ impl Transmuter<'_> { ctx: (DepsMut, Env, MessageInfo), pool_asset_denoms: Vec, lp_subdenom: String, + admin: Option, ) -> Result { let (deps, env, _info) = ctx; @@ -65,6 +69,12 @@ impl Transmuter<'_> { CREATE_LP_DENOM_REPLY_ID, ); + // set admin if provided + if let Some(admin) = admin { + let admin = deps.api.addr_validate(&admin)?; + self.admin.set(deps, Some(admin))?; + } + Ok(Response::new() .add_attribute("method", "instantiate") .add_attribute("contract_name", CONTRACT_NAME) @@ -88,6 +98,27 @@ impl Transmuter<'_> { } } + #[msg(exec)] + pub fn update_admin( + &self, + ctx: (DepsMut, Env, MessageInfo), + admin: Option, + ) -> Result { + let (deps, _env, info) = ctx; + + let new_admin = admin.map(|a| deps.api.addr_validate(&a)).transpose()?; + + self.admin + .execute_update_admin(deps, info, new_admin) + .map_err(Into::into) + } + + #[msg(query)] + pub fn query_admin(&self, ctx: (Deps, Env)) -> StdResult { + let (deps, _env) = ctx; + self.admin.query_admin(deps) + } + /// Join pool with tokens that exist in the pool. /// Token used to join pool is sent to the contract via `funds` in `MsgExecuteContract`. #[msg(exec)] diff --git a/contracts/transmuter/src/error.rs b/contracts/transmuter/src/error.rs index 110c3e4..1bc75e2 100644 --- a/contracts/transmuter/src/error.rs +++ b/contracts/transmuter/src/error.rs @@ -1,4 +1,5 @@ use cosmwasm_std::{Coin, Decimal, StdError, Uint128}; +use cw_controllers::AdminError; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -6,6 +7,9 @@ pub enum ContractError { #[error("{0}")] Std(#[from] StdError), + #[error("{0}")] + Admin(#[from] AdminError), + #[error("Funds must contain exactly one token")] SingleTokenExpected {}, diff --git a/contracts/transmuter/src/shares.rs b/contracts/transmuter/src/shares.rs index 6c6cb2e..39ffa53 100644 --- a/contracts/transmuter/src/shares.rs +++ b/contracts/transmuter/src/shares.rs @@ -6,10 +6,10 @@ pub struct Shares<'a> { share_denom: Item<'a, String>, } -impl Shares<'_> { - pub const fn new() -> Self { +impl<'a> Shares<'a> { + pub const fn new(namespace: &'a str) -> Self { Self { - share_denom: Item::new("share_denom"), + share_denom: Item::new(namespace), } } diff --git a/contracts/transmuter/src/test/cases/scenarios.rs b/contracts/transmuter/src/test/cases/scenarios.rs index d30a602..41ff19d 100644 --- a/contracts/transmuter/src/test/cases/scenarios.rs +++ b/contracts/transmuter/src/test/cases/scenarios.rs @@ -44,6 +44,7 @@ fn test_join_pool() { .with_instantiate_msg(InstantiateMsg { pool_asset_denoms: vec![AXL_USDC.to_string(), COSMOS_USDC.to_string()], lp_subdenom: "transmuter/poolshare".to_string(), + admin: None, }) .build(&app); @@ -206,6 +207,7 @@ fn test_swap() { .with_instantiate_msg(InstantiateMsg { pool_asset_denoms: vec![AXL_USDC.to_string(), COSMOS_USDC.to_string()], lp_subdenom: "transmuter/poolshare".to_string(), + admin: None, }) .build(&app); @@ -398,6 +400,7 @@ fn test_exit_pool() { .with_instantiate_msg(InstantiateMsg { pool_asset_denoms: vec![AXL_USDC.to_string(), COSMOS_USDC.to_string()], lp_subdenom: "transmuter/poolshare".to_string(), + admin: None, }) .build(&app); @@ -605,6 +608,7 @@ fn test_3_pool_swap() { COSMOS_USDC.to_string(), ], lp_subdenom: "transmuter/poolshare".to_string(), + admin: None, }) .build(&app); @@ -818,6 +822,7 @@ fn test_swap_lp_denom() { .with_instantiate_msg(InstantiateMsg { pool_asset_denoms: vec![AXL_ETH.to_string(), WH_ETH.to_string()], lp_subdenom: lp_subdenom.to_string(), + admin: None, }) .build(&app); diff --git a/contracts/transmuter/src/test/cases/units/join_and_exit.rs b/contracts/transmuter/src/test/cases/units/join_and_exit.rs index 91aeaeb..d7795da 100644 --- a/contracts/transmuter/src/test/cases/units/join_and_exit.rs +++ b/contracts/transmuter/src/test/cases/units/join_and_exit.rs @@ -65,6 +65,7 @@ fn test_join_pool_with_single_lp_should_update_shares_and_liquidity_properly() { .with_instantiate_msg(crate::contract::InstantiateMsg { pool_asset_denoms: vec!["denoma".to_string(), "denomb".to_string()], lp_subdenom: "transmuter/poolshare".to_string(), + admin: None, }) .build(&app); @@ -179,6 +180,7 @@ fn test_join_pool_should_update_shares_and_liquidity_properly() { .with_instantiate_msg(InstantiateMsg { pool_asset_denoms: vec!["denoma".to_string(), "denomb".to_string()], lp_subdenom: "transmuter/poolshare".to_string(), + admin: None, }) .build(&app); @@ -291,6 +293,7 @@ fn test_exit_pool_less_than_their_shares_should_update_shares_and_liquidity_prop .with_instantiate_msg(InstantiateMsg { pool_asset_denoms: vec!["denoma".to_string(), "denomb".to_string()], lp_subdenom: "transmuter/poolshare".to_string(), + admin: None, }) .build(&app); @@ -482,6 +485,7 @@ fn test_exit_pool_greater_than_their_shares_should_fail() { .with_instantiate_msg(InstantiateMsg { pool_asset_denoms: vec!["denoma".to_string(), "denomb".to_string()], lp_subdenom: "transmuter/poolshare".to_string(), + admin: None, }) .build(&app); @@ -528,6 +532,7 @@ fn test_exit_pool_within_shares_but_over_joined_denom_amount() { .with_instantiate_msg(InstantiateMsg { pool_asset_denoms: vec!["denoma".to_string(), "denomb".to_string()], lp_subdenom: "transmuter/poolshare".to_string(), + admin: None, }) .build(&app); diff --git a/contracts/transmuter/src/test/cases/units/spot_price.rs b/contracts/transmuter/src/test/cases/units/spot_price.rs index 5437083..ce5f123 100644 --- a/contracts/transmuter/src/test/cases/units/spot_price.rs +++ b/contracts/transmuter/src/test/cases/units/spot_price.rs @@ -26,6 +26,7 @@ fn test_spot_price(liquidity: &[Coin]) { (deps.as_mut(), mock_env(), mock_info("creator", &[])), vec!["denom0".to_string(), "denom1".to_string()], "transmuter/poolshare".to_string(), + None, ) .unwrap(); diff --git a/contracts/transmuter/src/test/cases/units/swap/mod.rs b/contracts/transmuter/src/test/cases/units/swap/mod.rs index 3181b1e..04f6b70 100644 --- a/contracts/transmuter/src/test/cases/units/swap/mod.rs +++ b/contracts/transmuter/src/test/cases/units/swap/mod.rs @@ -248,6 +248,7 @@ fn pool_with_single_lp(app: &'_ OsmosisTestApp, pool_assets: Vec) -> TestE .with_instantiate_msg(crate::contract::InstantiateMsg { pool_asset_denoms: pool_assets.iter().map(|c| c.denom.clone()).collect(), lp_subdenom: "transmuter/poolshare".to_string(), + admin: None, }) .build(app); From 76594ee3be97acfa2eaa51bc3ed272cd5f80bec0 Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Fri, 16 Jun 2023 15:56:26 +0700 Subject: [PATCH 4/5] expose set denom metadata --- contracts/transmuter/src/contract.rs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/contracts/transmuter/src/contract.rs b/contracts/transmuter/src/contract.rs index d283b1e..a046c30 100644 --- a/contracts/transmuter/src/contract.rs +++ b/contracts/transmuter/src/contract.rs @@ -5,8 +5,11 @@ use cosmwasm_std::{ }; use cw_controllers::{Admin, AdminResponse}; use cw_storage_plus::Item; -use osmosis_std::types::osmosis::tokenfactory::v1beta1::{ - MsgBurn, MsgCreateDenom, MsgCreateDenomResponse, MsgMint, +use osmosis_std::types::{ + cosmos::bank::v1beta1::Metadata, + osmosis::tokenfactory::v1beta1::{ + MsgBurn, MsgCreateDenom, MsgCreateDenomResponse, MsgMint, MsgSetDenomMetadata, + }, }; use sylvia::contract; @@ -119,6 +122,27 @@ impl Transmuter<'_> { self.admin.query_admin(deps) } + #[msg(exec)] + pub fn set_lp_denom_metadata( + &self, + ctx: (DepsMut, Env, MessageInfo), + metadata: Metadata, + ) -> Result { + let (deps, env, info) = ctx; + + // ensure admin + self.admin.assert_admin(deps.as_ref(), &info.sender)?; + + let msg_set_denom_metadata = MsgSetDenomMetadata { + sender: env.contract.address.to_string(), + metadata: Some(metadata), + }; + + Ok(Response::new() + .add_attribute("method", "set_lp_denom_metadata") + .add_message(msg_set_denom_metadata)) + } + /// Join pool with tokens that exist in the pool. /// Token used to join pool is sent to the contract via `funds` in `MsgExecuteContract`. #[msg(exec)] From 2a23bbbea0e09715133e23492dace088f33c7bf9 Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Fri, 16 Jun 2023 16:25:50 +0700 Subject: [PATCH 5/5] add tests for admin fns --- contracts/transmuter/src/contract.rs | 2 +- .../transmuter/src/test/cases/units/admin.rs | 165 ++++++++++++++++++ .../transmuter/src/test/cases/units/mod.rs | 1 + contracts/transmuter/src/test/test_env.rs | 20 ++- 4 files changed, 181 insertions(+), 7 deletions(-) create mode 100644 contracts/transmuter/src/test/cases/units/admin.rs diff --git a/contracts/transmuter/src/contract.rs b/contracts/transmuter/src/contract.rs index a046c30..b1118f4 100644 --- a/contracts/transmuter/src/contract.rs +++ b/contracts/transmuter/src/contract.rs @@ -117,7 +117,7 @@ impl Transmuter<'_> { } #[msg(query)] - pub fn query_admin(&self, ctx: (Deps, Env)) -> StdResult { + pub fn admin(&self, ctx: (Deps, Env)) -> StdResult { let (deps, _env) = ctx; self.admin.query_admin(deps) } diff --git a/contracts/transmuter/src/test/cases/units/admin.rs b/contracts/transmuter/src/test/cases/units/admin.rs new file mode 100644 index 0000000..7d73f3b --- /dev/null +++ b/contracts/transmuter/src/test/cases/units/admin.rs @@ -0,0 +1,165 @@ +use cosmwasm_std::Coin; +use cw_controllers::AdminResponse; +use osmosis_std::types::cosmos::bank::v1beta1::{ + DenomUnit, Metadata, QueryDenomMetadataRequest, QueryDenomMetadataResponse, +}; +use osmosis_test_tube::{Account, OsmosisTestApp, Runner}; + +use crate::{ + contract::{ExecMsg, GetShareDenomResponse, InstantiateMsg, QueryMsg}, + test::test_env::{assert_contract_err, TestEnvBuilder}, +}; + +const AXL_ETH: &str = "ibc/AXLETH"; +const WH_ETH: &str = "ibc/WHETH"; + +#[test] +fn test_manage_admin() { + let app = OsmosisTestApp::new(); + + let lp_subdenom = "transmuter/eth"; + let t = TestEnvBuilder::new() + .with_account("alice", vec![Coin::new(1_500, AXL_ETH)]) + .with_account( + "admin", + vec![Coin::new(100_000, AXL_ETH), Coin::new(100_000, WH_ETH)], + ) + .with_instantiate_msg(InstantiateMsg { + pool_asset_denoms: vec![AXL_ETH.to_string(), WH_ETH.to_string()], + lp_subdenom: lp_subdenom.to_string(), + admin: None, + }) + .with_admin("admin") + .build(&app); + + // set admin by non admin should fail + let err = t + .contract + .execute( + &ExecMsg::UpdateAdmin { + admin: Some(t.accounts["alice"].address()), + }, + &[], + &t.accounts["alice"], + ) + .unwrap_err(); + + assert_contract_err( + crate::ContractError::Admin(cw_controllers::AdminError::NotAdmin {}), + err, + ); + + // set admin by admin should succeed + t.contract + .execute( + &ExecMsg::UpdateAdmin { + admin: Some(t.accounts["alice"].address()), + }, + &[], + &t.accounts["admin"], + ) + .unwrap(); + + let AdminResponse { admin } = t.contract.query(&QueryMsg::Admin {}).unwrap(); + assert_eq!(admin, Some(t.accounts["alice"].address())); + + // remove admin by admin should succeed + t.contract + .execute( + &ExecMsg::UpdateAdmin { admin: None }, + &[], + &t.accounts["alice"], + ) + .unwrap(); + + let AdminResponse { admin } = t.contract.query(&QueryMsg::Admin {}).unwrap(); + assert_eq!(admin, None); +} + +#[test] +fn test_admin_set_denom_metadata() { + let app = OsmosisTestApp::new(); + + let lp_subdenom = "transmuter/eth"; + let t = TestEnvBuilder::new() + .with_account("alice", vec![Coin::new(1_500, AXL_ETH)]) + .with_account("bob", vec![Coin::new(1_500, WH_ETH)]) + .with_account( + "admin", + vec![Coin::new(100_000, AXL_ETH), Coin::new(100_000, WH_ETH)], + ) + .with_instantiate_msg(InstantiateMsg { + pool_asset_denoms: vec![AXL_ETH.to_string(), WH_ETH.to_string()], + lp_subdenom: lp_subdenom.to_string(), + admin: None, + }) + .with_admin("admin") + .build(&app); + + // pool share denom + let GetShareDenomResponse { share_denom } = + t.contract.query(&QueryMsg::GetShareDenom {}).unwrap(); + + assert_eq!( + format!("factory/{}/{}", t.contract.contract_addr, lp_subdenom), + share_denom + ); + + let metadata_to_set = Metadata { + base: share_denom.clone(), + description: "Canonical ETH".to_string(), + denom_units: vec![ + DenomUnit { + denom: share_denom.clone(), + exponent: 0, + aliases: vec!["ueth".to_string()], + }, + DenomUnit { + denom: "eth".to_string(), + exponent: 6, + aliases: vec![], + }, + ], + display: "eth".to_string(), + name: "Canonical ETH".to_string(), + symbol: "ETH".to_string(), + }; + + // set denom metadata by non admin should fail + let err = t + .contract + .execute( + &ExecMsg::SetLpDenomMetadata { + metadata: metadata_to_set.clone(), + }, + &[], + &t.accounts["alice"], + ) + .unwrap_err(); + + assert_contract_err( + crate::ContractError::Admin(cw_controllers::AdminError::NotAdmin {}), + err, + ); + + // set denom metadata + t.contract + .execute( + &ExecMsg::SetLpDenomMetadata { + metadata: metadata_to_set.clone(), + }, + &[], + &t.accounts["admin"], + ) + .unwrap(); + + // query denom metadata + let QueryDenomMetadataResponse { metadata } = app + .query( + "/cosmos.bank.v1beta1.Query/DenomMetadata", + &QueryDenomMetadataRequest { denom: share_denom }, + ) + .unwrap(); + + assert_eq!(metadata.unwrap(), metadata_to_set); +} diff --git a/contracts/transmuter/src/test/cases/units/mod.rs b/contracts/transmuter/src/test/cases/units/mod.rs index 4749bfe..58f0b1b 100644 --- a/contracts/transmuter/src/test/cases/units/mod.rs +++ b/contracts/transmuter/src/test/cases/units/mod.rs @@ -1,3 +1,4 @@ +mod admin; mod join_and_exit; mod spot_price; mod swap; diff --git a/contracts/transmuter/src/test/test_env.rs b/contracts/transmuter/src/test/test_env.rs index 8b038b4..e532e48 100644 --- a/contracts/transmuter/src/test/test_env.rs +++ b/contracts/transmuter/src/test/test_env.rs @@ -75,6 +75,7 @@ impl<'a> TestEnv<'a> { pub struct TestEnvBuilder { account_balances: HashMap>, instantiate_msg: Option, + admin: Option, } impl TestEnvBuilder { @@ -82,6 +83,7 @@ impl TestEnvBuilder { Self { account_balances: HashMap::new(), instantiate_msg: None, + admin: None, } } @@ -90,6 +92,11 @@ impl TestEnvBuilder { self } + pub fn with_admin(mut self, admin: &str) -> Self { + self.admin = Some(admin.to_string()); + self + } + pub fn with_account(mut self, account: &str, balance: Vec) -> Self { self.account_balances.insert(account.to_string(), balance); self @@ -112,12 +119,13 @@ impl TestEnvBuilder { .init_account(&[Coin::new(1000000000000000u128, "uosmo")]) .unwrap(); - let contract = TransmuterContract::deploy( - app, - &self.instantiate_msg.expect("instantiate msg not set"), - &creator, - ) - .unwrap(); + let instantiate_msg = self.instantiate_msg.expect("instantiate msg not set"); + let instantiate_msg = InstantiateMsg { + admin: accounts.get("admin").map(|admin| admin.address()), + ..instantiate_msg + }; + + let contract = TransmuterContract::deploy(app, &instantiate_msg, &creator).unwrap(); TestEnv { app,