From a749f294a7dbdb0fb55016df69648c9107dfdd6a Mon Sep 17 00:00:00 2001 From: Supanat Potiwarakorn Date: Fri, 1 Sep 2023 17:18:58 +0700 Subject: [PATCH] use alloyed asset terms --- contracts/transmuter/src/alloyed_asset.rs | 69 +++++++++++++ contracts/transmuter/src/contract.rs | 96 +++++++++++-------- contracts/transmuter/src/lib.rs | 2 +- contracts/transmuter/src/shares.rs | 60 ------------ contracts/transmuter/src/sudo.rs | 22 ++--- .../transmuter/src/test/cases/scenarios.rs | 4 +- .../transmuter/src/test/cases/units/admin.rs | 4 +- .../src/test/cases/units/spot_price.rs | 4 +- 8 files changed, 144 insertions(+), 117 deletions(-) create mode 100644 contracts/transmuter/src/alloyed_asset.rs delete mode 100644 contracts/transmuter/src/shares.rs diff --git a/contracts/transmuter/src/alloyed_asset.rs b/contracts/transmuter/src/alloyed_asset.rs new file mode 100644 index 0000000..242dcc3 --- /dev/null +++ b/contracts/transmuter/src/alloyed_asset.rs @@ -0,0 +1,69 @@ +use cosmwasm_std::{Addr, Coin, Deps, StdError, StdResult, Storage, Uint128}; +use cw_storage_plus::Item; +use osmosis_std::types::cosmos::bank::v1beta1::BankQuerier; + +/// Alloyed asset represents the shares of the pool +/// and since the pool is a 1:1 multi-asset pool, it act +/// as a composite of the underlying assets and assume 1:1 +/// value to the underlying assets. +pub struct AlloyedAsset<'a> { + alloyed_denom: Item<'a, String>, +} + +impl<'a> AlloyedAsset<'a> { + pub const fn new(alloyed_denom_namespace: &'a str) -> Self { + Self { + alloyed_denom: Item::new(alloyed_denom_namespace), + } + } + + /// get the alloyed denom + pub fn get_alloyed_denom(&self, store: &dyn Storage) -> StdResult { + self.alloyed_denom.load(store) + } + + /// set the alloyed denom + pub fn set_alloyed_denom( + &self, + store: &mut dyn Storage, + alloyed_denom: &String, + ) -> StdResult<()> { + self.alloyed_denom.save(store, alloyed_denom) + } + + /// get the total supply of alloyed asset + /// which is the total shares of the pool + pub fn get_total_supply(&self, deps: Deps) -> StdResult { + let alloyed_denom = self.get_alloyed_denom(deps.storage)?; + let bank_querier = BankQuerier::new(&deps.querier); + + bank_querier + .supply_of(alloyed_denom)? + .amount + .ok_or_else(|| StdError::generic_err("No shares"))? + .amount + .parse() + } + + /// get the balance of alloyed asset for a given address + pub fn get_balance(&self, deps: Deps, address: &Addr) -> StdResult { + let alloyed_denom = self.get_alloyed_denom(deps.storage)?; + let bank_querier = BankQuerier::new(&deps.querier); + + bank_querier + .balance(address.to_string(), alloyed_denom)? + .balance + .ok_or_else(|| StdError::generic_err("No alloyed asset"))? + .amount + .parse() + } + + /// calculate the amount of alloyed asset to mint + pub fn calc_amount_to_mint(tokens: &[Coin]) -> StdResult { + let mut total = Uint128::zero(); + for coin in tokens { + total = total.checked_add(coin.amount)?; + } + Ok(total) + } +} diff --git a/contracts/transmuter/src/contract.rs b/contracts/transmuter/src/contract.rs index 2665e29..6aca340 100644 --- a/contracts/transmuter/src/contract.rs +++ b/contracts/transmuter/src/contract.rs @@ -1,9 +1,9 @@ use crate::{ admin::Admin, + alloyed_asset::AlloyedAsset, ensure_admin_authority, error::ContractError, limiter::{Limiter, LimiterParams, Limiters}, - shares::Shares, transmuter_pool::TransmuterPool, }; use cosmwasm_schema::cw_serde; @@ -28,7 +28,7 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); /// Swap fee is hardcoded to zero intentionally. const SWAP_FEE: Decimal = Decimal::zero(); -const CREATE_LP_DENOM_REPLY_ID: u64 = 1; +const CREATE_ALLOYED_DENOM_REPLY_ID: u64 = 1; /// Prefix for alloyed asset denom const ALLOYED_PREFIX: &str = "alloyed"; @@ -36,7 +36,7 @@ const ALLOYED_PREFIX: &str = "alloyed"; pub struct Transmuter<'a> { pub(crate) active_status: Item<'a, bool>, pub(crate) pool: Item<'a, TransmuterPool>, - pub(crate) shares: Shares<'a>, + pub(crate) alloyed_asset: AlloyedAsset<'a>, pub(crate) admin: Admin<'a>, pub(crate) limiters: Limiters<'a>, } @@ -49,7 +49,7 @@ impl Transmuter<'_> { Self { active_status: Item::new("active_status"), pool: Item::new("pool"), - shares: Shares::new("share_denom"), + alloyed_asset: AlloyedAsset::new("alloyed_denom"), admin: Admin::new("admin"), limiters: Limiters::new("limiters"), } @@ -82,33 +82,33 @@ impl Transmuter<'_> { // set active status to true self.active_status.save(deps.storage, &true)?; - // create lp denom - let msg_create_lp_denom = SubMsg::reply_on_success( + // create alloyed denom + let msg_create_alloyed_denom = SubMsg::reply_on_success( MsgCreateDenom { sender: env.contract.address.to_string(), subdenom: format!("{}/{}", ALLOYED_PREFIX, alloyed_asset_subdenom), }, - CREATE_LP_DENOM_REPLY_ID, + CREATE_ALLOYED_DENOM_REPLY_ID, ); Ok(Response::new() .add_attribute("method", "instantiate") .add_attribute("contract_name", CONTRACT_NAME) .add_attribute("contract_version", CONTRACT_VERSION) - .add_submessage(msg_create_lp_denom)) + .add_submessage(msg_create_alloyed_denom)) } pub fn reply(&self, ctx: (DepsMut, Env), msg: Reply) -> Result { let (deps, _env) = ctx; match msg.id { - CREATE_LP_DENOM_REPLY_ID => { + CREATE_ALLOYED_DENOM_REPLY_ID => { // register created token denom let MsgCreateDenomResponse { new_token_denom } = msg.result.try_into()?; - self.shares - .set_share_denom(deps.storage, &new_token_denom)?; + self.alloyed_asset + .set_alloyed_denom(deps.storage, &new_token_denom)?; - Ok(Response::new().add_attribute("pool_share_denom", new_token_denom)) + Ok(Response::new().add_attribute("alloyed_denom", new_token_denom)) } _ => Err(StdError::not_found(format!("No reply handler found for: {:?}", msg)).into()), } @@ -250,7 +250,7 @@ impl Transmuter<'_> { } #[msg(exec)] - pub fn set_lp_denom_metadata( + pub fn set_alloyed_denom_metadata( &self, ctx: (DepsMut, Env, MessageInfo), metadata: Metadata, @@ -266,7 +266,7 @@ impl Transmuter<'_> { }; Ok(Response::new() - .add_attribute("method", "set_lp_denom_metadata") + .add_attribute("method", "set_alloyed_denom_metadata") .add_message(msg_set_denom_metadata)) } @@ -293,10 +293,10 @@ impl Transmuter<'_> { /// Token used to join pool is sent to the contract via `funds` in `MsgExecuteContract`. #[msg(exec)] pub fn join_pool(&self, ctx: (DepsMut, Env, MessageInfo)) -> Result { - self.swap_tokens_for_shares("join_pool", ctx) + self.swap_tokens_for_alloyed_asset("join_pool", ctx) } - pub fn swap_tokens_for_shares( + pub fn swap_tokens_for_alloyed_asset( &self, method: &str, ctx: (DepsMut, Env, MessageInfo), @@ -324,12 +324,12 @@ impl Transmuter<'_> { self.pool.save(deps.storage, &pool)?; - // mint lp tokens - let share_denom = self.shares.get_share_denom(deps.storage)?; - let new_shares = Shares::calc_shares(&info.funds)?; + // mint alloyed asset + let alloyed_denom = self.alloyed_asset.get_alloyed_denom(deps.storage)?; + let new_shares = AlloyedAsset::calc_amount_to_mint(&info.funds)?; let mint_msg = MsgMint { sender: env.contract.address.to_string(), - amount: Some(Coin::new(new_shares.u128(), share_denom).into()), + amount: Some(Coin::new(new_shares.u128(), alloyed_denom).into()), mint_to_address: info.sender.to_string(), }; @@ -347,10 +347,10 @@ impl Transmuter<'_> { ctx: (DepsMut, Env, MessageInfo), tokens_out: Vec, ) -> Result { - self.swap_shares_for_tokens("exit_pool", ctx, tokens_out) + self.swap_alloyed_asset_for_tokens("exit_pool", ctx, tokens_out) } - pub fn swap_shares_for_tokens( + pub fn swap_alloyed_asset_for_tokens( &self, method: &str, ctx: (DepsMut, Env, MessageInfo), @@ -358,20 +358,22 @@ impl Transmuter<'_> { ) -> Result { let (deps, env, info) = ctx; - let share_denom = self.shares.get_share_denom(deps.storage)?; + let alloyed_denom = self.alloyed_asset.get_alloyed_denom(deps.storage)?; - // if funds contains one coin and is share token, use that as sender's share + // if funds contains one coin and is alloyed asset, use that as sender's share let (sender_shares, burn_from_address) = if info.funds.is_empty() { - let sender_shares = self.shares.get_share(deps.as_ref(), &info.sender)?; + let sender_shares = self + .alloyed_asset + .get_balance(deps.as_ref(), &info.sender)?; let burn_from_address = info.sender.to_string(); (sender_shares, burn_from_address) } else { ensure!(info.funds.len() == 1, ContractError::SingleTokenExpected {}); ensure!( - info.funds[0].denom == share_denom, + info.funds[0].denom == alloyed_denom, ContractError::UnexpectedDenom { expected: info.funds[0].clone().denom, - actual: share_denom + actual: alloyed_denom } ); @@ -381,7 +383,7 @@ impl Transmuter<'_> { }; // check if sender's shares is enough - let required_shares = Shares::calc_shares(&tokens_out)?; + let required_shares = AlloyedAsset::calc_amount_to_mint(&tokens_out)?; ensure!( sender_shares >= required_shares, @@ -411,11 +413,11 @@ impl Transmuter<'_> { amount: tokens_out, }; - // burn lp tokens - let share_denom = self.shares.get_share_denom(deps.storage)?; + // burn alloyed assets + let alloyed_denom = self.alloyed_asset.get_alloyed_denom(deps.storage)?; let burn_msg = MsgBurn { sender: env.contract.address.to_string(), - amount: Some(Coin::new(required_shares.u128(), share_denom).into()), + amount: Some(Coin::new(required_shares.u128(), alloyed_denom).into()), burn_from_address, }; @@ -445,8 +447,8 @@ impl Transmuter<'_> { let (deps, _env) = ctx; Ok(GetSharesResponse { shares: self - .shares - .get_share(deps, &deps.api.addr_validate(&address)?)?, + .alloyed_asset + .get_balance(deps, &deps.api.addr_validate(&address)?)?, }) } @@ -457,7 +459,18 @@ impl Transmuter<'_> { ) -> Result { let (deps, _env) = ctx; Ok(GetShareDenomResponse { - share_denom: self.shares.get_share_denom(deps.storage)?, + share_denom: self.alloyed_asset.get_alloyed_denom(deps.storage)?, + }) + } + + #[msg(query)] + pub(crate) fn get_alloyed_denom( + &self, + ctx: (Deps, Env), + ) -> Result { + let (deps, _env) = ctx; + Ok(GetAlloyedDenomResponse { + alloyed_denom: self.alloyed_asset.get_alloyed_denom(deps.storage)?, }) } @@ -483,7 +496,7 @@ impl Transmuter<'_> { ctx: (Deps, Env), ) -> Result { let (deps, _env) = ctx; - let total_shares = self.shares.get_total_shares(deps)?; + let total_shares = self.alloyed_asset.get_total_supply(deps)?; Ok(GetTotalSharesResponse { total_shares }) } @@ -728,6 +741,11 @@ pub struct GetShareDenomResponse { pub share_denom: String, } +#[cw_serde] +pub struct GetAlloyedDenomResponse { + pub alloyed_denom: String, +} + #[cw_serde] pub struct GetSwapFeeResponse { pub swap_fee: Decimal, @@ -798,13 +816,13 @@ mod tests { // Instantiate the contract. instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap(); - // Manually set share denom - let share_denom = "uosmo".to_string(); + // Manually set alloyed denom + let alloyed_denom = "uosmo".to_string(); let transmuter = Transmuter::new(); transmuter - .shares - .set_share_denom(&mut deps.storage, &share_denom) + .alloyed_asset + .set_alloyed_denom(&mut deps.storage, &alloyed_denom) .unwrap(); // Check the initial active status. diff --git a/contracts/transmuter/src/lib.rs b/contracts/transmuter/src/lib.rs index dc90678..dd85353 100644 --- a/contracts/transmuter/src/lib.rs +++ b/contracts/transmuter/src/lib.rs @@ -1,8 +1,8 @@ mod admin; +mod alloyed_asset; pub mod contract; mod error; mod limiter; -mod shares; mod sudo; mod transmuter_pool; pub use crate::error::ContractError; diff --git a/contracts/transmuter/src/shares.rs b/contracts/transmuter/src/shares.rs deleted file mode 100644 index 8209195..0000000 --- a/contracts/transmuter/src/shares.rs +++ /dev/null @@ -1,60 +0,0 @@ -use cosmwasm_std::{Addr, Coin, Deps, StdError, StdResult, Storage, Uint128}; -use cw_storage_plus::Item; -use osmosis_std::types::cosmos::bank::v1beta1::BankQuerier; - -pub struct Shares<'a> { - share_denom: Item<'a, String>, -} - -impl<'a> Shares<'a> { - pub const fn new(share_denom_namespace: &'a str) -> Self { - Self { - share_denom: Item::new(share_denom_namespace), - } - } - - /// get the share denom - pub fn get_share_denom(&self, store: &dyn Storage) -> StdResult { - self.share_denom.load(store) - } - - /// set the share denom - pub fn set_share_denom(&self, store: &mut dyn Storage, share_denom: &String) -> StdResult<()> { - self.share_denom.save(store, share_denom) - } - - /// get the total shares - pub fn get_total_shares(&self, deps: Deps) -> StdResult { - let share_denom = self.get_share_denom(deps.storage)?; - let bank_querier = BankQuerier::new(&deps.querier); - - bank_querier - .supply_of(share_denom)? - .amount - .ok_or_else(|| StdError::generic_err("No shares"))? - .amount - .parse() - } - - /// get shares for a given address - pub fn get_share(&self, deps: Deps, address: &Addr) -> StdResult { - let share_denom = self.get_share_denom(deps.storage)?; - let bank_querier = BankQuerier::new(&deps.querier); - - bank_querier - .balance(address.to_string(), share_denom)? - .balance - .ok_or_else(|| StdError::generic_err("No shares"))? - .amount - .parse() - } - - /// calculate the amount of shares for a given amount of tokens - pub fn calc_shares(tokens: &[Coin]) -> StdResult { - let mut total = Uint128::zero(); - for coin in tokens { - total = total.checked_add(coin.amount)?; - } - Ok(total) - } -} diff --git a/contracts/transmuter/src/sudo.rs b/contracts/transmuter/src/sudo.rs index 88a5e3b..b981b9a 100644 --- a/contracts/transmuter/src/sudo.rs +++ b/contracts/transmuter/src/sudo.rs @@ -59,17 +59,17 @@ impl SudoMsg { let (deps, env) = ctx; let sender = deps.api.addr_validate(&sender)?; - let share_denom = transmuter.shares.get_share_denom(deps.storage)?; + let alloyed_denom = transmuter.alloyed_asset.get_alloyed_denom(deps.storage)?; - // if token in is share denom, swap shares for tokens - if token_in.denom == share_denom { + // if token in is share denom, swap alloyed asset for tokens + if token_in.denom == alloyed_denom { let token_out = Coin::new(token_in.amount.u128(), token_out_denom); let swap_result = to_binary(&SwapExactAmountInResponseData { token_out_amount: token_out.amount, })?; return transmuter - .swap_shares_for_tokens( + .swap_alloyed_asset_for_tokens( method, ( deps, @@ -85,14 +85,14 @@ impl SudoMsg { } // if token out is share denom, swap token for shares - if token_out_denom == share_denom { + if token_out_denom == alloyed_denom { let token_out = Coin::new(token_in.amount.u128(), token_out_denom); let swap_result = to_binary(&SwapExactAmountInResponseData { token_out_amount: token_out.amount, })?; return transmuter - .swap_tokens_for_shares( + .swap_tokens_for_alloyed_asset( method, ( deps, @@ -160,17 +160,17 @@ impl SudoMsg { let sender = deps.api.addr_validate(&sender)?; - let share_denom = transmuter.shares.get_share_denom(deps.storage)?; + let alloyed_denom = transmuter.alloyed_asset.get_alloyed_denom(deps.storage)?; // if token in is share denom, swap shares for tokens - if token_in_denom == share_denom { + if token_in_denom == alloyed_denom { let token_in = Coin::new(token_out.amount.u128(), token_in_denom); let swap_result = to_binary(&SwapExactAmountOutResponseData { token_in_amount: token_in.amount, })?; return transmuter - .swap_shares_for_tokens( + .swap_alloyed_asset_for_tokens( method, ( deps, @@ -186,14 +186,14 @@ impl SudoMsg { } // if token out is share denom, swap token for shares - if token_out.denom == share_denom { + if token_out.denom == alloyed_denom { let token_in = Coin::new(token_out.amount.u128(), token_in_denom); let swap_result = to_binary(&SwapExactAmountOutResponseData { token_in_amount: token_in.amount, })?; return transmuter - .swap_tokens_for_shares( + .swap_tokens_for_alloyed_asset( method, ( deps, diff --git a/contracts/transmuter/src/test/cases/scenarios.rs b/contracts/transmuter/src/test/cases/scenarios.rs index 71f94e5..dd9cb11 100644 --- a/contracts/transmuter/src/test/cases/scenarios.rs +++ b/contracts/transmuter/src/test/cases/scenarios.rs @@ -875,7 +875,7 @@ fn test_3_pool_swap() { } #[test] -fn test_swap_lp_denom() { +fn test_swap_alloyed_asset() { let app = OsmosisTestApp::new(); let alloyed_asset_subdenom = "eth"; @@ -893,7 +893,7 @@ fn test_swap_lp_denom() { }) .build(&app); - // pool share denom + // alloyed asset denom let GetShareDenomResponse { share_denom } = t.contract.query(&QueryMsg::GetShareDenom {}).unwrap(); diff --git a/contracts/transmuter/src/test/cases/units/admin.rs b/contracts/transmuter/src/test/cases/units/admin.rs index 1599dd3..1b9da5c 100644 --- a/contracts/transmuter/src/test/cases/units/admin.rs +++ b/contracts/transmuter/src/test/cases/units/admin.rs @@ -69,7 +69,7 @@ fn test_admin_set_denom_metadata() { let err = t .contract .execute( - &ExecMsg::SetLpDenomMetadata { + &ExecMsg::SetAlloyedDenomMetadata { metadata: metadata_to_set.clone(), }, &[], @@ -82,7 +82,7 @@ fn test_admin_set_denom_metadata() { // set denom metadata t.contract .execute( - &ExecMsg::SetLpDenomMetadata { + &ExecMsg::SetAlloyedDenomMetadata { metadata: metadata_to_set.clone(), }, &[], diff --git a/contracts/transmuter/src/test/cases/units/spot_price.rs b/contracts/transmuter/src/test/cases/units/spot_price.rs index ce5f123..8f670eb 100644 --- a/contracts/transmuter/src/test/cases/units/spot_price.rs +++ b/contracts/transmuter/src/test/cases/units/spot_price.rs @@ -31,8 +31,8 @@ fn test_spot_price(liquidity: &[Coin]) { .unwrap(); transmuter - .shares - .set_share_denom( + .alloyed_asset + .set_alloyed_denom( &mut deps.storage, &"factory/contract_address/transmuter/poolshare".to_string(), )