Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

simulation #27

Merged
merged 21 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <[email protected]>"]
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
Loading