diff --git a/.github/workflows/scripts.yml b/.github/workflows/scripts.yml index e3a1525d0..ea2681f4e 100644 --- a/.github/workflows/scripts.yml +++ b/.github/workflows/scripts.yml @@ -26,7 +26,7 @@ jobs: uses: davidB/rust-cargo-make@v1 - name: Install stable Rust - run: cd ../ && cargo make install-stable && cd scripts + run: cd ../ && cargo make install-stable-for-scripts && cd scripts # selecting a toolchain should happen before the plugin, as the cache uses the current rustc version as its cache key - name: Cache dependencies diff --git a/Cargo.lock b/Cargo.lock index bd4681f2c..8232b4d78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2114,7 +2114,7 @@ dependencies = [ [[package]] name = "mars-credit-manager" -version = "2.0.0" +version = "2.0.1" dependencies = [ "anyhow", "cosmwasm-schema", diff --git a/Makefile.toml b/Makefile.toml index a27c7d1ae..baf5f1eeb 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -24,6 +24,10 @@ rustup component add clippy --toolchain ${RUST_VERSION} rustup component add llvm-tools-preview --toolchain ${RUST_VERSION} ''' +[tasks.install-stable-for-scripts] +env = { RUST_VERSION = "1.72.0" } +run_task = "install-stable" + [tasks.install-nightly] script = ''' curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain nightly diff --git a/contracts/credit-manager/Cargo.toml b/contracts/credit-manager/Cargo.toml index 54f550145..554259935 100644 --- a/contracts/credit-manager/Cargo.toml +++ b/contracts/credit-manager/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mars-credit-manager" -version = { workspace = true } +version = "2.0.1" authors = { workspace = true } license = { workspace = true } edition = { workspace = true } diff --git a/contracts/credit-manager/src/contract.rs b/contracts/credit-manager/src/contract.rs index d8a0e5443..18bf186c2 100644 --- a/contracts/credit-manager/src/contract.rs +++ b/contracts/credit-manager/src/contract.rs @@ -128,5 +128,6 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult { pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> ContractResult { match msg { MigrateMsg::V1_0_0ToV2_0_0(updates) => migrations::v2_0_0::migrate(deps, env, updates), + MigrateMsg::V2_0_0ToV2_0_1 {} => migrations::v2_0_1::migrate(deps), } } diff --git a/contracts/credit-manager/src/migrations/mod.rs b/contracts/credit-manager/src/migrations/mod.rs index 7592b6f12..24a4db4ab 100644 --- a/contracts/credit-manager/src/migrations/mod.rs +++ b/contracts/credit-manager/src/migrations/mod.rs @@ -1 +1,2 @@ pub mod v2_0_0; +pub mod v2_0_1; diff --git a/contracts/credit-manager/src/migrations/v2_0_1.rs b/contracts/credit-manager/src/migrations/v2_0_1.rs new file mode 100644 index 000000000..777b1e365 --- /dev/null +++ b/contracts/credit-manager/src/migrations/v2_0_1.rs @@ -0,0 +1,21 @@ +use cosmwasm_std::{DepsMut, Response}; +use cw2::{assert_contract_version, set_contract_version}; + +use crate::{ + contract::{CONTRACT_NAME, CONTRACT_VERSION}, + error::ContractError, +}; + +const FROM_VERSION: &str = "2.0.0"; + +pub fn migrate(deps: DepsMut) -> Result { + // make sure we're migrating the correct contract and from the correct version + assert_contract_version(deps.storage, &format!("crates.io:{CONTRACT_NAME}"), FROM_VERSION)?; + + set_contract_version(deps.storage, format!("crates.io:{CONTRACT_NAME}"), CONTRACT_VERSION)?; + + Ok(Response::new() + .add_attribute("action", "migrate") + .add_attribute("from_version", FROM_VERSION) + .add_attribute("to_version", CONTRACT_VERSION)) +} diff --git a/contracts/credit-manager/src/repay.rs b/contracts/credit-manager/src/repay.rs index 729e43076..af1f98e06 100644 --- a/contracts/credit-manager/src/repay.rs +++ b/contracts/credit-manager/src/repay.rs @@ -9,7 +9,7 @@ use mars_types::credit_manager::{ActionCoin, CallbackMsg::Repay, ExecuteMsg}; use crate::{ error::{ContractError, ContractResult}, - state::{DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}, + state::{COIN_BALANCES, DEBT_SHARES, RED_BANK, TOTAL_DEBT_SHARES}, utils::{debt_shares_to_amount, decrement_coin_balance, increment_coin_balance}, }; @@ -17,7 +17,9 @@ pub fn repay(deps: DepsMut, account_id: &str, coin: &ActionCoin) -> ContractResu // Ensure repayment does not exceed max debt on account let (debt_amount, debt_shares) = current_debt_for_denom(deps.as_ref(), account_id, &coin.denom)?; - let amount_to_repay = min(debt_amount, coin.amount.value().unwrap_or(Uint128::MAX)); + let coin_balance = + COIN_BALANCES.may_load(deps.storage, (account_id, &coin.denom))?.unwrap_or_default(); + let amount_to_repay = min(debt_amount, coin.amount.value().unwrap_or(coin_balance)); let coin_to_repay = Coin { denom: coin.denom.to_string(), amount: amount_to_repay, @@ -86,7 +88,10 @@ pub fn repay_for_recipient( ) -> ContractResult { let (debt_amount, _) = current_debt_for_denom(deps.as_ref(), recipient_account_id, &coin.denom)?; - let amount_to_repay = min(debt_amount, coin.amount.value().unwrap_or(Uint128::MAX)); + let coin_balance = COIN_BALANCES + .may_load(deps.storage, (benefactor_account_id, &coin.denom))? + .unwrap_or_default(); + let amount_to_repay = min(debt_amount, coin.amount.value().unwrap_or(coin_balance)); let coin_to_repay = &Coin { denom: coin.denom, amount: amount_to_repay, diff --git a/contracts/credit-manager/tests/tests/test_migration_v2.rs b/contracts/credit-manager/tests/tests/test_migration_v2.rs index 21bd22a8b..9cf3f6039 100644 --- a/contracts/credit-manager/tests/tests/test_migration_v2.rs +++ b/contracts/credit-manager/tests/tests/test_migration_v2.rs @@ -1,8 +1,9 @@ use cosmwasm_std::{ + attr, testing::{mock_dependencies, mock_env}, - Addr, Decimal, + Addr, Decimal, Event, }; -use cw2::VersionError; +use cw2::{ContractVersion, VersionError}; use mars_credit_manager::{ contract::migrate, error::ContractError, @@ -148,3 +149,26 @@ fn successful_migration() { let set_red_bank = RED_BANK.load(deps.as_ref().storage).unwrap(); assert_eq!(old_red_bank, set_red_bank.addr.as_str()); } + +#[test] +fn successful_migration_to_v2_0_1() { + let mut deps = mock_dependencies(); + cw2::set_contract_version(deps.as_mut().storage, "crates.io:mars-credit-manager", "2.0.0") + .unwrap(); + + let res = migrate(deps.as_mut(), mock_env(), MigrateMsg::V2_0_0ToV2_0_1 {}).unwrap(); + + assert_eq!(res.messages, vec![]); + assert_eq!(res.events, vec![] as Vec); + assert!(res.data.is_none()); + assert_eq!( + res.attributes, + vec![attr("action", "migrate"), attr("from_version", "2.0.0"), attr("to_version", "2.0.1")] + ); + + let new_contract_version = ContractVersion { + contract: "crates.io:mars-credit-manager".to_string(), + version: "2.0.1".to_string(), + }; + assert_eq!(cw2::get_contract_version(deps.as_ref().storage).unwrap(), new_contract_version); +} diff --git a/contracts/credit-manager/tests/tests/test_repay.rs b/contracts/credit-manager/tests/tests/test_repay.rs index 8461ea8d6..9903cbe6c 100644 --- a/contracts/credit-manager/tests/tests/test_repay.rs +++ b/contracts/credit-manager/tests/tests/test_repay.rs @@ -10,6 +10,7 @@ use mars_types::{ use super::helpers::{ assert_err, uosmo_info, AccountToFund, CoinInfo, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, }; +use crate::tests::helpers::{get_coin, get_debt, uatom_info}; #[test] fn only_token_owner_can_repay() { @@ -389,3 +390,55 @@ fn amount_none_repays_total_debt() { let coin = mock.query_balance(&Addr::unchecked(config.red_bank), &coin_info.denom); assert_eq!(coin.amount, DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(1))); } + +#[test] +fn amount_none_repays_no_more_than_available_asset() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + + let user = Addr::unchecked("user"); + + let mut mock = MockEnv::new() + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: user.clone(), + funds: coins(300, uatom_info.denom.clone()), + }) + .build() + .unwrap(); + + let account_id = mock.create_credit_account(&user).unwrap(); + + mock.update_credit_account( + &account_id, + &user, + vec![ + Deposit(uatom_info.to_coin(300)), + Borrow(uosmo_info.to_coin(50)), + Withdraw(uosmo_info.to_action_coin(10)), + Repay { + recipient_account_id: None, + coin: uosmo_info.to_action_coin_full_balance(), + }, + ], + &[uatom_info.to_coin(300)], + ) + .unwrap(); + + let position = mock.query_positions(&account_id); + assert_eq!(position.debts.len(), 1); + let uosmo_debt = get_debt(&uosmo_info.denom, &position.debts); + // debt: 50 uosmo, + // account balance: 40 uosmo (50 borrowed - 10 withdrawn) + // repaying full balance should repay 40 uosmo + assert_eq!(uosmo_debt.amount, Uint128::new(11)); // 10 + 1 interest + + assert_eq!(position.deposits.len(), 1); + assert_eq!(get_coin(&uatom_info.denom, &position.deposits), uatom_info.to_coin(300)); + + let coin = mock.query_balance(&mock.rover, &uatom_info.denom); + assert_eq!(coin.amount, Uint128::new(300)); + + let coin = mock.query_balance(&mock.rover, &uosmo_info.denom); + assert_eq!(coin.amount, Uint128::zero()); +} diff --git a/contracts/credit-manager/tests/tests/test_repay_for_recipient.rs b/contracts/credit-manager/tests/tests/test_repay_for_recipient.rs index eab550b40..772c8e9e2 100644 --- a/contracts/credit-manager/tests/tests/test_repay_for_recipient.rs +++ b/contracts/credit-manager/tests/tests/test_repay_for_recipient.rs @@ -8,8 +8,9 @@ use mars_types::credit_manager::{ }; use super::helpers::{ - assert_err, uosmo_info, AccountToFund, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, + assert_err, uatom_info, uosmo_info, AccountToFund, MockEnv, DEFAULT_RED_BANK_COIN_BALANCE, }; +use crate::tests::helpers::{get_coin, get_debt}; #[test] fn only_rover_can_call_repay_for_recipient_callback() { @@ -392,3 +393,65 @@ fn benefactor_attempts_to_pay_more_than_max_debt() { let coin = mock.query_balance(&Addr::unchecked(config.red_bank), &coin_info.denom); assert_eq!(coin.amount, DEFAULT_RED_BANK_COIN_BALANCE.add(Uint128::new(1))); } + +#[test] +fn amount_none_repays_no_more_than_available_asset() { + let uosmo_info = uosmo_info(); + let uatom_info = uatom_info(); + + let recipient = Addr::unchecked("recipient"); + let benefactor = Addr::unchecked("benefactor"); + + let mut mock = MockEnv::new() + .set_params(&[uosmo_info.clone(), uatom_info.clone()]) + .fund_account(AccountToFund { + addr: benefactor.clone(), + funds: coins(300, uosmo_info.denom.clone()), + }) + .fund_account(AccountToFund { + addr: recipient.clone(), + funds: coins(300, uatom_info.denom.clone()), + }) + .build() + .unwrap(); + + let recipient_account_id = mock.create_credit_account(&recipient).unwrap(); + let benefactor_account_id = mock.create_credit_account(&benefactor).unwrap(); + + mock.update_credit_account( + &recipient_account_id, + &recipient, + vec![Deposit(uatom_info.to_coin(300)), Borrow(uosmo_info.to_coin(60))], + &[coin(300, uatom_info.denom.clone())], + ) + .unwrap(); + + mock.update_credit_account( + &benefactor_account_id, + &benefactor, + vec![ + Deposit(uosmo_info.to_coin(50)), + Repay { + recipient_account_id: Some(recipient_account_id.clone()), + coin: uosmo_info.to_action_coin_full_balance(), + }, + ], + &[coin(50, uosmo_info.denom.clone())], + ) + .unwrap(); + + let recipient_position = mock.query_positions(&recipient_account_id.clone()); + assert_eq!(recipient_position.deposits.len(), 2); + assert_eq!(get_coin(&uatom_info.denom, &recipient_position.deposits), uatom_info.to_coin(300)); + assert_eq!(get_coin(&uosmo_info.denom, &recipient_position.deposits), uosmo_info.to_coin(60)); + assert_eq!(recipient_position.debts.len(), 1); + let uosmo_debt = get_debt(&uosmo_info.denom, &recipient_position.debts); + // recipient debt: 60 uosmo, + // benefactor account balance: 50 uosmo + // repaying with benefactor full balance, should repay 50 uosmo + assert_eq!(uosmo_debt.amount, Uint128::new(11)); // 10 + 1 interest + + let benefactor_position = mock.query_positions(&benefactor_account_id.clone()); + assert_eq!(benefactor_position.deposits.len(), 0); + assert_eq!(benefactor_position.debts.len(), 0); +} diff --git a/packages/types/src/credit_manager/migrate.rs b/packages/types/src/credit_manager/migrate.rs index a35c64f85..ae6fffacf 100644 --- a/packages/types/src/credit_manager/migrate.rs +++ b/packages/types/src/credit_manager/migrate.rs @@ -18,4 +18,5 @@ pub struct V2Updates { #[cw_serde] pub enum MigrateMsg { V1_0_0ToV2_0_0(V2Updates), + V2_0_0ToV2_0_1 {}, } diff --git a/schemas/mars-credit-manager/mars-credit-manager.json b/schemas/mars-credit-manager/mars-credit-manager.json index c6f17a25a..4254357a3 100644 --- a/schemas/mars-credit-manager/mars-credit-manager.json +++ b/schemas/mars-credit-manager/mars-credit-manager.json @@ -1,6 +1,6 @@ { "contract_name": "mars-credit-manager", - "contract_version": "2.0.0", + "contract_version": "2.0.1", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#",