Skip to content

Commit

Permalink
Merge pull request #25 from osmosis-labs/feat/add-pool-assets
Browse files Browse the repository at this point in the history
feat: add new assets
  • Loading branch information
iboss-ptk authored Nov 7, 2023
2 parents 3df15a4 + 26fa489 commit a628bc5
Show file tree
Hide file tree
Showing 11 changed files with 527 additions and 137 deletions.
172 changes: 49 additions & 123 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions contracts/transmuter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
authors = ["Supanat Potiwarakorn <supanat.ptk@gmail.com>"]
edition = "2021"
name = "transmuter"
version = "2.0.0"
version = "2.1.0"

exclude = [
# Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication.
Expand Down Expand Up @@ -45,12 +45,12 @@ cosmwasm-std = {version = "1.3.1", features = ["cosmwasm_1_1"]}
cosmwasm-storage = "1.3.1"
cw-storage-plus = "1.1.0"
cw2 = "1.1.0"
osmosis-std = "0.19.1"
osmosis-std = "0.20.1"
schemars = "0.8.12"
serde = {version = "1.0.183", default-features = false, features = ["derive"]}
sylvia = "0.7.0"
thiserror = {version = "1.0.44"}

[dev-dependencies]
itertools = "0.11.0"
osmosis-test-tube = "19.0.0"
osmosis-test-tube = "20.1.0"
154 changes: 152 additions & 2 deletions contracts/transmuter/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use osmosis_std::types::{
use sylvia::contract;

/// version info for migration
const CONTRACT_NAME: &str = "crates.io:transmuter";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
pub const CONTRACT_NAME: &str = "crates.io:transmuter";
pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");

/// Swap fee is hardcoded to zero intentionally.
const SWAP_FEE: Decimal = Decimal::zero();
Expand Down Expand Up @@ -131,6 +131,40 @@ impl Transmuter<'_> {

// === executes ===

#[msg(exec)]
fn add_new_assets(
&self,
ctx: (DepsMut, Env, MessageInfo),
denoms: Vec<String>,
) -> Result<Response, ContractError> {
let (deps, _env, info) = ctx;

// only admin can add new assets
ensure_admin_authority!(info.sender, self.role.admin, deps.as_ref());

// ensure that new denoms are not alloyed denom
let share_denom = self.alloyed_asset.get_alloyed_denom(deps.storage)?;
for denom in &denoms {
ensure!(
denom != &share_denom,
ContractError::ShareDenomNotAllowedAsPoolAsset {}
);
}

// convert denoms to Denom type
let denoms = denoms
.into_iter()
.map(|denom| Denom::validate(deps.as_ref(), denom))
.collect::<Result<Vec<_>, ContractError>>()?;

// add new assets to the pool
let mut pool = self.pool.load(deps.storage)?;
pool.add_new_assets(&denoms)?;
self.pool.save(deps.storage, &pool)?;

Ok(Response::new().add_attribute("method", "add_new_assets"))
}

#[msg(exec)]
fn register_limiter(
&self,
Expand Down Expand Up @@ -997,6 +1031,122 @@ mod tests {
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use cosmwasm_std::{attr, from_binary, SubMsgResponse, SubMsgResult, Uint64};

#[test]
fn test_add_new_assets() {
let mut deps = mock_dependencies();

// make denom has non-zero total supply
deps.querier.update_balance(
"someone",
vec![
Coin::new(1, "uosmo"),
Coin::new(1, "uion"),
Coin::new(1, "new_asset1"),
Coin::new(1, "new_asset2"),
],
);

let admin = "admin";
let init_msg = InstantiateMsg {
pool_asset_denoms: vec!["uosmo".to_string(), "uion".to_string()],
alloyed_asset_subdenom: "uosmouion".to_string(),
admin: Some(admin.to_string()),
moderator: None,
};
let env = mock_env();
let info = mock_info(admin, &[]);

// Instantiate the contract.
instantiate(deps.as_mut(), env.clone(), info.clone(), init_msg).unwrap();

// Manually reply
let alloyed_denom = "usomoion";

reply(
deps.as_mut(),
env.clone(),
Reply {
id: 1,
result: SubMsgResult::Ok(SubMsgResponse {
events: vec![],
data: Some(
MsgCreateDenomResponse {
new_token_denom: alloyed_denom.to_string(),
}
.into(),
),
}),
},
)
.unwrap();

// Add new assets

// Attempt to add assets with invalid denom
let invalid_denoms = vec!["invalid_asset1".to_string(), "invalid_asset2".to_string()];
let add_invalid_assets_msg = ContractExecMsg::Transmuter(ExecMsg::AddNewAssets {
denoms: invalid_denoms,
});

let res = execute(
deps.as_mut(),
env.clone(),
info.clone(),
add_invalid_assets_msg,
);

// Check if the attempt resulted in DenomHasNoSupply error
assert_eq!(
res.unwrap_err(),
ContractError::DenomHasNoSupply {
denom: "invalid_asset1".to_string()
}
);

let new_assets = vec!["new_asset1".to_string(), "new_asset2".to_string()];
let add_assets_msg =
ContractExecMsg::Transmuter(ExecMsg::AddNewAssets { denoms: new_assets });

// Attempt to add assets by non-admin
let non_admin_info = mock_info("non_admin", &[]);
let res = execute(
deps.as_mut(),
env.clone(),
non_admin_info,
add_assets_msg.clone(),
);

// Check if the attempt was unauthorized
assert_eq!(
res.unwrap_err(),
ContractError::Unauthorized {},
"Adding assets by non-admin should be unauthorized"
);

execute(deps.as_mut(), env.clone(), info, add_assets_msg).unwrap();

// Check if the new assets were added
let res = query(
deps.as_ref(),
env.clone(),
ContractQueryMsg::Transmuter(QueryMsg::GetTotalPoolLiquidity {}),
)
.unwrap();
let GetTotalPoolLiquidityResponse {
total_pool_liquidity,
} = from_binary(&res).unwrap();

assert_eq!(
total_pool_liquidity,
vec![
Coin::new(0, "uosmo"),
Coin::new(0, "uion"),
Coin::new(0, "new_asset1"),
Coin::new(0, "new_asset2"),
]
);
}

#[test]
fn test_set_active_status() {
let mut deps = mock_dependencies();
Expand Down
11 changes: 10 additions & 1 deletion contracts/transmuter/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),

#[error("{0}")]
VersionError(#[from] cw2::VersionError),

#[error("Funds must be empty")]
EmptyFundsExpected {},

Expand Down Expand Up @@ -80,9 +83,15 @@ pub enum ContractError {
#[error("The pool is currently inactive")]
InactivePool {},

#[error("YUnexpected denom: expected: {expected}, actual: {actual}")]
#[error("Unexpected denom: expected: {expected}, actual: {actual}")]
UnexpectedDenom { expected: String, actual: String },

#[error("Duplicated pool asset denom: {denom}")]
DuplicatedPoolAssetDenom { denom: String },

#[error("Pool asset not be share denom")]
ShareDenomNotAllowedAsPoolAsset {},

#[error("Unauthorized")]
Unauthorized {},

Expand Down
11 changes: 11 additions & 0 deletions contracts/transmuter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod contract;
mod denom;
mod error;
mod limiter;
mod migrations;
mod role;
mod sudo;
mod transmuter_pool;
Expand All @@ -19,6 +20,7 @@ mod entry_points {

use crate::contract::{ContractExecMsg, ContractQueryMsg, ExecMsg, InstantiateMsg, Transmuter};
use crate::error::ContractError;
use crate::migrations;
use crate::sudo::SudoMsg;

const CONTRACT: Transmuter = Transmuter::new();
Expand Down Expand Up @@ -87,6 +89,15 @@ mod entry_points {

msg.dispatch(&CONTRACT, (deps, env))
}

#[entry_point]
pub fn migrate(
deps: DepsMut,
_env: Env,
msg: migrations::v2_1_0::MigrateMsg,
) -> Result<Response, ContractError> {
migrations::v2_1_0::execute_migration(deps, msg)
}
}

#[cfg(not(feature = "library"))]
Expand Down
1 change: 1 addition & 0 deletions contracts/transmuter/src/migrations/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod v2_1_0;
34 changes: 34 additions & 0 deletions contracts/transmuter/src/migrations/v2_1_0.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{ensure_eq, DepsMut, Response};

use crate::{
contract::{CONTRACT_NAME, CONTRACT_VERSION},
ContractError,
};

const FROM_VERSION: &str = "2.0.0";
const TO_VERSION: &str = "2.1.0";

#[cw_serde]
pub struct MigrateMsg {}

pub fn execute_migration(deps: DepsMut, _msg: MigrateMsg) -> Result<Response, ContractError> {
// Assert that the stored contract version matches the expected version before migration
cw2::assert_contract_version(deps.storage, CONTRACT_NAME, FROM_VERSION)?;

// Ensure that the current contract version matches the target version to prevent migration to an incorrect version
ensure_eq!(
CONTRACT_VERSION,
TO_VERSION,
cw2::VersionError::WrongVersion {
expected: TO_VERSION.to_string(),
found: CONTRACT_VERSION.to_string()
}
);

// Set the contract version to the target version after successful migration
cw2::set_contract_version(deps.storage, CONTRACT_NAME, TO_VERSION)?;

// Return a response with an attribute indicating the method that was executed
Ok(Response::new().add_attribute("method", "v2_1_0/execute_migraiton"))
}
97 changes: 97 additions & 0 deletions contracts/transmuter/src/test/cases/units/add_new_assets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use cosmwasm_std::Coin;
use osmosis_test_tube::OsmosisTestApp;

use crate::{
contract::{GetShareDenomResponse, GetTotalPoolLiquidityResponse, InstantiateMsg},
test::test_env::{assert_contract_err, TestEnvBuilder},
ContractError,
};

#[test]
fn test_add_new_assets() {
let app = OsmosisTestApp::new();

// create denom
app.init_account(&[
Coin::new(1, "denom1"),
Coin::new(1, "denom2"),
Coin::new(1, "denom3"),
Coin::new(1, "denom4"),
])
.unwrap();

let t = TestEnvBuilder::new()
.with_account("admin", vec![])
.with_account("non_admin", vec![])
.with_instantiate_msg(InstantiateMsg {
pool_asset_denoms: vec!["denom1".to_string(), "denom2".to_string()],
admin: None, // override by admin account set above
alloyed_asset_subdenom: "denomx".to_string(),
moderator: None,
})
.build(&app);

// add new asset
let denoms = vec!["denom3".to_string(), "denom4".to_string()];

let err = t
.contract
.execute(
&crate::contract::ExecMsg::AddNewAssets {
denoms: denoms.clone(),
},
&[],
&t.accounts["non_admin"],
)
.unwrap_err();

assert_contract_err(ContractError::Unauthorized {}, err);

t.contract
.execute(
&crate::contract::ExecMsg::AddNewAssets { denoms },
&[],
&t.accounts["admin"],
)
.unwrap();

// Get total pool liquidity
let GetTotalPoolLiquidityResponse {
total_pool_liquidity,
} = t
.contract
.query(&crate::contract::QueryMsg::GetTotalPoolLiquidity {})
.unwrap();

assert_eq!(
total_pool_liquidity,
vec![
Coin::new(0, "denom1"),
Coin::new(0, "denom2"),
Coin::new(0, "denom3"),
Coin::new(0, "denom4"),
]
);

// Get alloyed denom
let GetShareDenomResponse {
share_denom: alloyed_denom,
} = t
.contract
.query(&crate::contract::QueryMsg::GetShareDenom {})
.unwrap();

// Attempt to add alloyed_denom as asset, should error
let err = t
.contract
.execute(
&crate::contract::ExecMsg::AddNewAssets {
denoms: vec![alloyed_denom.clone()],
},
&[],
&t.accounts["admin"],
)
.unwrap_err();

assert_contract_err(ContractError::ShareDenomNotAllowedAsPoolAsset {}, err);
}
2 changes: 2 additions & 0 deletions contracts/transmuter/src/test/cases/units/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ mod spot_price;
mod swap;

mod create_pool;

mod add_new_assets;
Loading

0 comments on commit a628bc5

Please sign in to comment.