Halborn Logo

Protocol - Kryptonite


Prepared by:

Halborn Logo

HALBORN

Last Updated 04/26/2024

Date of Engagement by: August 28th, 2023 - November 1st, 2023

Summary

100% of all REPORTED Findings have been addressed

All findings

52

Critical

9

High

7

Medium

6

Low

18

Informational

12


Table of Contents

1. INTRODUCTION

Kryptonite engaged Halborn to conduct a security assessment on their smart contracts beginning on 2023-08-28 and ending on 2023-11-01. The security assessment was scoped to the smart contracts provided to the Halborn team.

2. ASSESSMENT SUMMARY

The team at Halborn assigned two full-time security engineers to verify the security of the smart contracts. The security engineers are blockchain and smart-contract security experts with advanced penetration testing, smart-contract hacking, and deep knowledge of multiple blockchain protocols.

The purpose of this assessment is to:

    • Ensure that smart contract functions operate as intended

    • Identify potential security issues with the smart contracts

In summary, Halborn identified some improvements to reduce the likelihood and impact of risks, which were mostly addressed by the \client team. The main ones were the following:

    • Add an adequate access control in the central_control, custody, custody_base, custody_bsei, overseer and stable_pool contracts.

    • Decrease users balance for each liquidated collateral.

    • Update the calculation of the maximum loan-to-value when minting coins.

    • Query the balance of the stable_pool contract when liquidating collaterals.

    • Add the "!" symbol when validating the bid and liquidation fees.

    • Update the calculation of the maximum loan-to-value when querying about available collaterals.

    • Include an execution message to bond rewards.

    • Include an execution message to swap all native tokens to reward denomination.

    • Validate the balance change when processing the withdraw rate.

    • Handle a list with pending redelegations and implement a public function to trigger the redelegation process over this list.

    • Verify if there is any bid already submitted before updating the maximum amount of slots.

3. SCOPE

CosmWasm Smart Contracts in scope:

. Kryptonite bAsset Contracts:

#. Repository: [krp-basset-convert](https://github.com/KryptoniteDAO/krp-basset-convert)
#. Commit ID: [56c7ccf](https://github.com/KryptoniteDAO/krp-basset-convert/tree/56c7ccfa2b2c0c3e59d147aed8542104a2a768a5)
#. Contracts in scope: 
    - krp_basset_converter
    - krp_basset_token

. Kryptonite CDP Contracts:

#. Repository: [krp-cdp-contracts](https://github.com/KryptoniteDAO/krp-cdp-contracts)
#. Commit ID: [8023786](https://github.com/KryptoniteDAO/krp-cdp-contracts/tree/80237864456a0c2c0c0a8e234eca3c56dddeca9c)
#. Contracts in scope: 
    - central_control
    - custody
    - liquidation_queue
    - reward_book
    - stable_pool

. Kryptonite Market Contracts:

#. Repository: [krp-market-contracts](https://github.com/KryptoniteDAO/krp-market-contracts)
#. Commit ID: [bbaafdc](https://github.com/KryptoniteDAO/krp-market-contracts/tree/bbaafdc521d463b2fd0b1eea7951522e17bfe39a)
#. Contracts in scope: 
    - custody_base
    - custody_bsei
    - distribution_model
    - interest_model
    - liquidation_queue
    - market
    - overseer

. Kryptonite Oracle:

#. Repository: [krp-oracle](https://github.com/KryptoniteDAO/krp-oracle)
#. Commit ID: [96de964](https://github.com/KryptoniteDAO/krp-oracle/tree/96de96490ff0f722c16a40f7fc8ac4c51d2423e9)
#. Contract in scope: 
    - oracle_pyth

. Kryptonite Staking Contracts:

#. Repository: [krp-staking-contracts](https://github.com/KryptoniteDAO/krp-staking-contracts)
#. Commit ID: [f58a4d4](https://github.com/KryptoniteDAO/krp-staking-contracts/tree/f58a4d4e22e2ebf16c5ac2b512c4c7f1d9e58242)
#. Contracts in scope: 
    - basset_sei_hub
    - basset_sei_reward
    - basset_sei_rewards_dispatcher
    - basset_sei_token_bsei
    - basset_sei_token_stsei
    - basset_sei_validators_registry

. Kryptonite Token Contracts:

#. Repository: [krp-token-contracts](https://github.com/KryptoniteDAO/krp-token-contracts)
#. Commit ID: [1b55b39](https://github.com/KryptoniteDAO/krp-token-contracts/tree/1b55b3992d3989d0375dc9ee45cf554d8600d047)
#. Contracts in scope: 
    - boost
    - dispatcher
    - distribute
    - fund
    - keeper
    - seilor
    - staking
    - treasure
    - ve_seilor

. Swap Extension:

#. Repository: [swap-extension](https://github.com/KryptoniteDAO/swap-extension)
#. Commit ID: [2aeb0a7](https://github.com/KryptoniteDAO/swap-extension/tree/2aeb0a777ffad2de1002728159ff0dee144f7bf5)
#. Contract in scope: 
    - swap_sparrow

Out-of-scope: External libraries and financial related attacks.

4. TEST APPROACH & METHODOLOGY

Halborn performed a combination of manual review of the code and automated security testing to balance efficiency, timeliness, practicality, and accuracy in regard to the scope of the smart contract assessment. While manual testing is recommended to uncover flaws in logic, process, and implementation; automated testing techniques help enhance coverage of smart contracts and can quickly identify items that do not follow security best practices. The following phases and associated tools were used throughout the term of the assessment:

    • Research into architecture, purpose, and use of the platform.

    • Manual code read and walkthrough.

    • Manual assessment of use and safety for the critical Rust variables and functions in scope to identify any contracts logic related vulnerability.

    • Fuzz testing (Halborn custom fuzzing tool)

    • Checking the test coverage (cargo tarpaulin)

    • Scanning of Rust files for vulnerabilities (cargo audit)

5. RISK METHODOLOGY

Every vulnerability and issue observed by Halborn is ranked based on two sets of Metrics and a Severity Coefficient. This system is inspired by the industry standard Common Vulnerability Scoring System.
The two Metric sets are: Exploitability and Impact. Exploitability captures the ease and technical means by which vulnerabilities can be exploited and Impact describes the consequences of a successful exploit.
The Severity Coefficients is designed to further refine the accuracy of the ranking with two factors: Reversibility and Scope. These capture the impact of the vulnerability on the environment as well as the number of users and smart contracts affected.
The final score is a value between 0-10 rounded up to 1 decimal place and 10 corresponding to the highest security risk. This provides an objective and accurate rating of the severity of security vulnerabilities in smart contracts.
The system is designed to assist in identifying and prioritizing vulnerabilities based on their level of risk to address the most critical issues in a timely manner.

5.1 EXPLOITABILITY

Attack Origin (AO):
Captures whether the attack requires compromising a specific account.
Attack Cost (AC):
Captures the cost of exploiting the vulnerability incurred by the attacker relative to sending a single transaction on the relevant blockchain. Includes but is not limited to financial and computational cost.
Attack Complexity (AX):
Describes the conditions beyond the attacker’s control that must exist in order to exploit the vulnerability. Includes but is not limited to macro situation, available third-party liquidity and regulatory challenges.
Metrics:
EXPLOITABILIY METRIC (mem_e)METRIC VALUENUMERICAL VALUE
Attack Origin (AO)Arbitrary (AO:A)
Specific (AO:S)
1
0.2
Attack Cost (AC)Low (AC:L)
Medium (AC:M)
High (AC:H)
1
0.67
0.33
Attack Complexity (AX)Low (AX:L)
Medium (AX:M)
High (AX:H)
1
0.67
0.33
Exploitability EE is calculated using the following formula:

E=meE = \prod m_e

5.2 IMPACT

Confidentiality (C):
Measures the impact to the confidentiality of the information resources managed by the contract due to a successfully exploited vulnerability. Confidentiality refers to limiting access to authorized users only.
Integrity (I):
Measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of data stored and/or processed on-chain. Integrity impact directly affecting Deposit or Yield records is excluded.
Availability (A):
Measures the impact to the availability of the impacted component resulting from a successfully exploited vulnerability. This metric refers to smart contract features and functionality, not state. Availability impact directly affecting Deposit or Yield is excluded.
Deposit (D):
Measures the impact to the deposits made to the contract by either users or owners.
Yield (Y):
Measures the impact to the yield generated by the contract for either users or owners.
Metrics:
IMPACT METRIC (mIm_I)METRIC VALUENUMERICAL VALUE
Confidentiality (C)None (I:N)
Low (I:L)
Medium (I:M)
High (I:H)
Critical (I:C)
0
0.25
0.5
0.75
1
Integrity (I)None (I:N)
Low (I:L)
Medium (I:M)
High (I:H)
Critical (I:C)
0
0.25
0.5
0.75
1
Availability (A)None (A:N)
Low (A:L)
Medium (A:M)
High (A:H)
Critical (A:C)
0
0.25
0.5
0.75
1
Deposit (D)None (D:N)
Low (D:L)
Medium (D:M)
High (D:H)
Critical (D:C)
0
0.25
0.5
0.75
1
Yield (Y)None (Y:N)
Low (Y:L)
Medium (Y:M)
High (Y:H)
Critical (Y:C)
0
0.25
0.5
0.75
1
Impact II is calculated using the following formula:

I=max(mI)+mImax(mI)4I = max(m_I) + \frac{\sum{m_I} - max(m_I)}{4}

5.3 SEVERITY COEFFICIENT

Reversibility (R):
Describes the share of the exploited vulnerability effects that can be reversed. For upgradeable contracts, assume the contract private key is available.
Scope (S):
Captures whether a vulnerability in one vulnerable contract impacts resources in other contracts.
Metrics:
SEVERITY COEFFICIENT (CC)COEFFICIENT VALUENUMERICAL VALUE
Reversibility (rr)None (R:N)
Partial (R:P)
Full (R:F)
1
0.5
0.25
Scope (ss)Changed (S:C)
Unchanged (S:U)
1.25
1
Severity Coefficient CC is obtained by the following product:

C=rsC = rs

The Vulnerability Severity Score SS is obtained by:

S=min(10,EIC10)S = min(10, EIC * 10)

The score is rounded up to 1 decimal places.
SeverityScore Value Range
Critical9 - 10
High7 - 8.9
Medium4.5 - 6.9
Low2 - 4.4
Informational0 - 1.9

6. SCOPE

Out-of-Scope: New features/implementations after the remediation commit IDs.

7. Assessment Summary & Findings Overview

Critical

9

High

7

Medium

6

Low

18

Informational

12

Security analysisRisk levelRemediation Date
LOANS CAN BE REPAID WITHOUT SPENDING COINSCriticalSolved - 10/18/2023
ARBITRARY MINTING OF COINS WITHOUT DEPOSITING COLLATERALSCriticalSolved - 10/18/2023
NO ACCESS CONTROL IN WITHDRAW FUNCTIONCriticalSolved - 10/21/2023
NO ACCESS CONTROL IN REPAYMENT ENTRY POINTCriticalSolved - 10/21/2023
ARBITRARY MINTING OF COINS USING FAKE CW20 TOKENSCriticalSolved - 10/18/2023
COLLATERAL BALANCE OF LIQUIDATED USERS DOES NOT DECREASECriticalSolved - 10/19/2023
LIQUIDATED LOANS WITHOUT AN ADEQUATE REPAYMENTCriticalSolved - 10/18/2023
ARBITRARY REPAYMENT OF COINS FROM LIQUIDATIONSCriticalSolved - 10/18/2023
MISCALCULATION OF MAX LOAN TO VALUE WHEN MINTING COINSCriticalSolved - 10/18/2023
COLLATERAL LIQUIDATION IS NOT WORKING AS EXPECTEDHighSolved - 10/18/2023
BID FEE IS NOT SENTHighSolved - 10/19/2023
LIQUIDATOR FEE IS NOT SENTHighSolved - 10/19/2023
MISCALCULATION OF MAX LOAN TO VALUE WHEN QUERYING AVAILABLE COLLATERALHighSolved - 10/18/2023
REWARDS BONDING FUNCTIONALITY IS UNAVAILABLEHighSolved - 10/19/2023
WITHOUT POSSIBILITY TO SWAP ALL NATIVE TOKENS TO REWARD DENOMINATIONHighSolved - 10/19/2023
UNCHECKED BALANCE CHANGE COULD LEAD TO UNFAIR WITHDRAWALSHighSolved - 10/19/2023
INADEQUATE TRACKING OF PENDING REDELEGATIONSMediumSolved - 10/19/2023
MAX NUMBER OF BID SLOTS COULD BE MODIFIED AFTER BIDS WERE SUBMITTEDMediumRisk Accepted - 11/01/2023
SOME FUNCTIONS RECEIVE MULTIPLE NATIVE COINS INSTEAD OF ONEMediumSolved - 10/24/2023
REDELEGATION IS NOT RESTRICTED TO ACTIVE VALIDATORSMediumSolved - 10/20/2023
COIN DENOMINATION IS NOT CHECKED WHEN REMOVING VALIDATORSMediumRisk Accepted - 10/24/2023
INADEQUATE TRACKING OF SWAPPED AMOUNTSMediumSolved - 10/23/2023
UNBOND WAIT LISTS CAN BE MIGRATED EVEN IF THE CONTRACT IS NOT PAUSEDLowSolved - 10/20/2023
UNCHECKED MAX LOAN-TO-VALUE RATIOLowSolved - 10/19/2023
FUNCTIONALITY TO UPDATE FEES IS NOT APPLIED CORRECTLYLowSolved - 10/20/2023
ARBITRARY MESSAGES CAN BE EXECUTED ON BEHALF OF THE HUBLowSolved - 10/20/2023
UNRELIABLE SOURCE OF RANDOMNESSLowSolved - 10/30/2023
WITHDRAWAL COULD GET STUCK IF THERE ARE NO MORE UNBONDINGSLowRisk Accepted - 10/24/2023
OWNERSHIP CAN BE TRANSFERRED WITHOUT CONFIRMATIONLowSolved - 10/26/2023
COMMENTED TRANSACTION MESSAGELowSolved - 11/21/2023
UNCHECKED REDEEM FEELowSolved - 10/18/2023
UNCHECKED KEEPER RATELowSolved - 10/20/2023
MAXIMUM AMOUNT OF TOKENS TO MINT IS NOT VALIDATEDLowSolved - 10/25/2023
UNCHECKED PARAMETERS IN DISPATCHER CONTRACTLowSolved - 10/25/2023
UNCHECKED PARAMETERS IN TREASURE CONTRACTLowSolved - 10/30/2023
DURATION IS NOT VALIDATED IN STAKING CONTRACTLowSolved - 10/30/2023
CLAIMABLE TIME IS NOT VALIDATED IN FUND CONTRACTLowSolved - 10/31/2023
EXCHANGE RATE COULD INCREASE INDEFINITELYLowSolved - 10/20/2023
PAIR KEY CAN CONTAIN DUPLICATED ASSETSLowSolved - 10/23/2023
ASSETS COULD MISMATCH WITH THE ONES IN PAIR ADDRESSLowSolved - 10/24/2023
IMMUTABLE VARIABLES CAN BE CHANGED IN STAKING CONTRACTInformationalSolved - 10/30/2023
MARKETING INFO IS NOT VALIDATED AT INSTANTIATIONInformationalSolved - 10/25/2023
LOCK AMOUNT IN GLOBAL STATE IS NOT VALIDATED WHEN CLAIMINGInformationalSolved - 10/25/2023
UNCHECKED VALIDATOR ADDRESSESInformationalSolved - 10/20/2023
UNCHECKED SAFE RATIOInformationalSolved - 10/19/2023
REDUNDANT LOGICInformationalSolved - 10/21/2023
REPEATED EXECUTION MESSAGESInformationalAcknowledged - 10/24/2023
STAKING TOKEN CALLS CHECK SLASHING TWICEInformationalSolved - 10/24/2023
UNIMPLEMENTED MESSAGEInformationalSolved - 10/20/2023
UNUSED MESSAGESInformationalSolved - 10/25/2023
UNUSED VARIABLESInformationalSolved - 10/30/2023
USELESS FUNCTIONSInformationalAcknowledged - 10/21/2023

8. Findings & Tech Details

8.1 LOANS CAN BE REPAID WITHOUT SPENDING COINS

// Critical

Description

The RepayStableCoin entry point of the krp-cdp-contracts/central_control contract allows users to repay their loans.

The entry point mentioned is designed to be called by the krp-cdp-contracts/stable_pool contract. However, there is no access control in the repay_stable_coin function to verify this condition.

As a consequence, anyone can call this function to repay their loans without spending coins.

Code Location

There is no access control in the repay_stable_coin function:

krp-cdp-contracts/contracts/central_control/src/contract.rs

pub fn repay_stable_coin(
 deps: DepsMut,
 _info: MessageInfo,
 sender: String,
 amount: Uint128,
) -> Result<Response, ContractError> {
 let minter_raw = deps.api.addr_canonicalize(&sender.as_str())?;
 let mut loan_info = read_minter_loan_info(deps.storage, &minter_raw)?;
 loan_info.loans = loan_info.loans - Uint256::from(amount);
 store_minter_loan_info(deps.storage, &minter_raw, &loan_info)?;

 Ok(Response::new().add_attributes(vec![
  attr("contract_name", "central_control"),
  attr("action", "repay_stable_coin"),
  attr("sender", sender.to_string()),
  attr("amount", amount.to_string()),
 ]))
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 64d3a2d.

8.2 ARBITRARY MINTING OF COINS WITHOUT DEPOSITING COLLATERALS

// Critical

Description

The MintStableCoin entry point of the krp-cdp-contracts/central_control contract mints stable coins to users according to their deposited collaterals.

The entry point mentioned is designed to be called by the krp-cdp-contracts/custody contract. However, there is no access control in the mint_stable_coin function to verify this condition.

As a consequence, anyone can call this function to mint stable coins to himself/herself without depositing collaterals.

Code Location

There is no access control in the mint_stable_coin function:

krp-cdp-contracts/contracts/central_control/src/contract.rs

pub fn mint_stable_coin(
 deps: DepsMut,
 _info: MessageInfo,
 minter: String,
 stable_amount: Uint128,
 collateral_amount: Option<Uint128>,
 collateral_contract: Option<String>,
 is_redemption_provider: Option<bool>,
) -> Result<Response, ContractError> {
 let config = read_config(deps.as_ref().storage)?;
 let api = deps.api;

 let minter_raw = api.addr_canonicalize(minter.as_str())?;
 let mut cur_collaterals: Tokens = read_collaterals(deps.storage, &minter_raw);

 let mut messages: Vec<CosmosMsg> = vec![];
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 4c5310b.

8.3 NO ACCESS CONTROL IN WITHDRAW FUNCTION

// Critical

Description

The WithdrawCollateral entry point of the krp-market-contracts/custody_base and krp-market-contracts/custody_bsei contracts allows a borrower to withdraw any amount of previously deposited collateral.

This entry point is designed to be called by the krp-market-contracts/overseer contract; however, there is no access control in the withdraw_collateral function to verify that.

As a consequence, any attacker could withdraw any amount of collateral from any other user, since the borrower and amount values are taken from the entry point input.

Code Location

Code fragment of the withdraw_collateral function of the krp-market-contracts/custody_bsei contract:

krp-market-contracts/contracts/custody_bsei/src/collateral.rs

pub fn withdraw_collateral(
    deps: DepsMut,
    borrower: String,
    amount: Option<Uint256>,
) -> Result<Response, ContractError> {
    let config: Config = read_config(deps.storage)?;

    let borrower_raw = deps.api.addr_canonicalize(borrower.as_str())?;
    let mut borrower_info: BorrowerInfo = read_borrower_info(deps.storage, &borrower_raw);

    // Check spendable balance
    let amount = amount.unwrap_or(borrower_info.spendable);
    if borrower_info.spendable < amount {
        return Err(ContractError::WithdrawAmountExceedsSpendable(
            borrower_info.spendable.into(),
        ));
    }

    // decrease borrower collateral
    borrower_info.balance = borrower_info.balance - amount;
    borrower_info.spendable = borrower_info.spendable - amount;

    if borrower_info.balance == Uint256::zero() {
        remove_borrower_info(deps.storage, &borrower_raw);
    } else {
        store_borrower_info(deps.storage, &borrower_raw, &borrower_info)?;
    }

    Ok(Response::new()
        .add_message(CosmosMsg::Wasm(WasmMsg::Execute {
            contract_addr: deps
                .api
                .addr_humanize(&config.collateral_token)?
                .to_string(),
            funds: vec![],
            msg: to_binary(&Cw20ExecuteMsg::Transfer {
                recipient: borrower.to_string(),
                amount: amount.into(),
            })?,
        }))
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 717dbe0.

8.4 NO ACCESS CONTROL IN REPAYMENT ENTRY POINT

// Critical

Description

The RepayStableFromYieldReserve entry point of the krp-market-contracts/overseer contract allows a borrower to repay his debt using the stable currency reserve accumulated in the contract.

This entry point has no access control, consequently, any user could repay their own debt without spending any coin, using the balance of the Overseer contract if it is sufficient.

Code Location

There is no access control in the repay_stable_from_yield_reserve function:

krp-market-contracts/contracts/overseer/src/collateral.rs

pub fn repay_stable_from_yield_reserve(
    deps: DepsMut,
    env: Env,
    _info: MessageInfo,
    borrower: Addr,
) -> Result<Response, ContractError> {
    let config: Config = read_config(deps.storage)?;
    let market = deps.api.addr_humanize(&config.market_contract)?;
    let borrow_amount_res: BorrowerInfoResponse = query_borrower_info(
        deps.as_ref(),
        market.clone(),
        borrower.clone(),
        env.block.height,
    )?;
    let borrow_amount = borrow_amount_res.loan_amount;

    let prev_balance: Uint256 = query_balance(
        deps.as_ref(),
        market.clone(),
        config.stable_denom.to_owned(),
    )?;

    let repay_messages = vec![
        CosmosMsg::Bank(BankMsg::Send {
            to_address: market.to_string(),
            amount: vec![Coin {
                denom: config.stable_denom,
                amount: borrow_amount.into(),
            }],
        }),
        CosmosMsg::Wasm(WasmMsg::Execute {
            contract_addr: market.to_string(),
            funds: vec![],
            msg: to_binary(&MarketExecuteMsg::RepayStableFromLiquidation {
                borrower: borrower.to_string(),
                prev_balance,
            })?,
        }),
    ];

    Ok(Response::new().add_messages(repay_messages))
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit e43051d.

8.5 ARBITRARY MINTING OF COINS USING FAKE CW20 TOKENS

// Critical

Description

The MintStableCoin entry point of the krp-cdp-contracts/custody contract mints stable coins to users according to their deposited collaterals.

The entry point mentioned is designed to be called by the target collateral contract. However, there is no access control in the mint_stable_coin function to verify this condition.

As a consequence, anyone could call this function to mint coins for himself/herself using a fake CW20 token.

Code Location

There is no access control in the mint_stable_coin function:

krp-cdp-contracts/contracts/custody/src/contract.rs

pub fn mint_stable_coin(
 deps: DepsMut,
 _info: MessageInfo,
 sender: String,
 amount: Uint128,
 stable_amount: Uint128,
 is_redemption_provider: Option<bool>,
) -> Result<Response, ContractError> {
 let config = read_config(deps.as_ref().storage)?;
 let api = deps.api;
 let control_contract = api.addr_humanize(&config.control_contract)?.to_string();

 let mut state = read_state(deps.as_ref().storage)?;
 state.total_amount = state.total_amount + Uint256::from(amount);
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 1e6a609.

8.6 COLLATERAL BALANCE OF LIQUIDATED USERS DOES NOT DECREASE

// Critical

Description

In the liquidate_collateral function from the krp-cdp-contracts/central_control contract, when each collateral in the liquidation_amount vector is liquidated, the DecreaseBalance message is not called.

As a consequence, the collateral balance of the liquidated user won't decrease and will continue accruing rewards.

Code Location

The DecreaseBalance message is not called in the liquidate_collateral function:

krp-cdp-contracts/contracts/central_control/src/contract.rs

for collateral in liquidation_amount {
 if collateral.1 > Uint256::zero() {
  let whitelist_elem = read_whitelist_elem(deps.storage, &collateral.0)?;
  liquidation_messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
   contract_addr: deps
    .api
    .addr_humanize(&whitelist_elem.custody_contract)?
    .to_string(),
   funds: vec![],
   msg: to_binary(&CustodyExecuteMsg::LiquidateCollateral {
    liquidator: info.sender.to_string(),
    amount: collateral.1.into(),
   })?,
  }))
 }
}
BVSS
Recommendation

SOLVED:The Kryptonite team solved this issue in commit 4a00da9.

8.7 LIQUIDATED LOANS WITHOUT AN ADEQUATE REPAYMENT

// Critical

Description

In the liquidate_collateral function from the krp-cdp-contracts/custody contract, there is no validation that info.sender is the krp-cdp-contracts/central_control contract. As a consequence, the coins to be used as repayment will only be transferred to the krp-cdp-contracts/stable_pool contract, without further logic to be executed. Later, an attacker could force the repayment of those coins to an arbitrary account, as shown in the following example:

  1. The attacker calls liquidate_collateral function in the custody contract.
  2. An amount of stable coins are transferred from the liquidation_queue contract to the stable_pool contract.
  3. The attacker forces the repayment of coins to himself, as described in the vulnerability \vulnref{ARBITRARY REPAYMENT OF COINS FROM LIQUIDATIONS}.

Code Location

There is no access control in the liquidate_collateral function:

krp-cdp-contracts/contracts/custody/src/contract.rs

pub fn liquidate_collateral(
 deps: DepsMut,
 _info: MessageInfo,
 liquidator: Addr,
 amount: Uint128,
) -> Result<Response, ContractError> {
 let config = read_config(deps.storage)?;

 let mut state = read_state(deps.storage)?;
 state.total_amount = state.total_amount - Uint256::from(amount);
 store_state(deps.storage, &state)?;
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 2997c00.

8.8 ARBITRARY REPAYMENT OF COINS FROM LIQUIDATIONS

// Critical

Description

In the repay_stable_from_liquidation function from the krp-cdp-contracts/stable_pool contract, there is no validation that info.sender is the krp-cdp-contracts/central_control contract. As a consequence, an attacker could repay coins to himself after liquidating other users' loans, as shown in the following example:

  1. The attacker gets an extremely low loan.
  2. The attacker liquidates some users and forces the transfer of coins to the stable_pool contract, as described in the vulnerability \vulnref{ARBITRARY REPAYMENT OF COINS FROM LIQUIDATIONS}.
  3. The attacker calls repay_stable_from_liquidation function from the stable_pool contract using pre_balance = 0 and minter = .
  4. Because the loan for the attacker is extremely low, almost all the coins will be transferred from the stable_pool contract to the attacker address.

Code Location

There is no access control in the repay_stable_from_liquidation function:

krp-cdp-contracts/contracts/stable_pool/src/contract.rs

pub fn repay_stable_from_liquidation(
 deps: DepsMut,
 env: Env,
 info: MessageInfo,
 minter: Addr,
 pre_balance: Uint256,
) -> Result<Response<SeiMsg>, ContractError> {
 let config = read_config(deps.storage)?;

 let cur_balance: Uint256 = query_balance(
  deps.as_ref(),
  env.contract.address.clone(),
  config.stable_denom.to_string(),
 )?;

 let mut info = info;
 info.sender = minter;

 info.funds = vec![Coin {
  denom: config.stable_denom,
  amount: (cur_balance - pre_balance).into(),
 }];

 repay_stable_coin(deps, info)
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 41ae7eb.

8.9 MISCALCULATION OF MAX LOAN TO VALUE WHEN MINTING COINS

// Critical

Description

In the mint_stable_coin function from krp-cdp-contracts/central_control contract, the max loan to value is miscalculated because the collaterals_values is increased in every step of the loop instead of storing the temporal value. As a consequence, users can borrow more coins than they should.

Currently, the value of collaterals_values is:

\begin{math} collaterals_values += Uint256::from(collateral.1) * price.emv_price; \end{math}

However, it should be:

\begin{math} collaterals_values = Uint256::from(collateral.1) * price.emv_price \end{math}

Code Location

krp-cdp-contracts/contracts/central_control/src/contract.rs

for collateral in cur_collaterals {
 let price = query_price(
  deps.as_ref(),
  deps.api.addr_humanize(&config.oracle_contract)?,
  api.addr_humanize(&collateral.0)?.to_string(),
  "".to_string(),
  None,
 )?;
 collaterals_values += Uint256::from(collateral.1) * price.emv_price;

 let collateral_info = read_whitelist_elem(deps.storage, &collateral.0)?;
 max_loan_to_value += collaterals_values * collateral_info.max_ltv;
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 9e26c73.

8.10 COLLATERAL LIQUIDATION IS NOT WORKING AS EXPECTED

// High

Description

The liquidate_collateral function in the krp-cdp-contracts/central_control contract is querying its own balance instead of the balance of krp-cdp-contracts/stable_pool contract. As a consequence, some unexpected situations can happen:

  • Trying to repay with an erroneous amount of stable coins.
  • Collateral liquidation fails, and the operation is reverted.

Code Location

krp-cdp-contracts/contracts/central_control/src/contract.rs

let pre_balance: Uint256 = query_balance(
 deps.as_ref(),
 env.contract.address.clone(),
 config.stable_denom.to_string(),
)?;
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 2b1e9bb.

8.11 BID FEE IS NOT SENT

// High

Description

The execute_liquidation function in the krp-cdp-contracts/liquidation_queue and krp-market-contracts/liquidation_queue contracts is responsible for executing the liquidation of the debt using the previously deposited bids.

There is an error in the code at the time of sending the bid_fee: the zero validation is incorrect, resulting in a situation where the fee will only be sent if it is equal to zero. This validation should incorporate the ! symbol before the condition to make sense.

Code Location

Code fragment of the execute_liquidation function in the krp-market-contracts/liquidation_queue contract:

krp-market-contracts/contracts/liquidation_queue/src/bid.rs

let mut messages: Vec<CosmosMsg> = vec![CosmosMsg::Bank(BankMsg::Send {
    to_address: repay_address,
    amount: vec![deduct_tax(
        deps.as_ref(),
        Coin {
            denom: config.stable_denom.clone(),
            amount: repay_amount.into(),
        },
    )?],
})];

if bid_fee.is_zero() {
    messages.push(CosmosMsg::Bank(BankMsg::Send {
        to_address: fee_address,
        amount: vec![deduct_tax(
            deps.as_ref(),
            Coin {
                denom: config.stable_denom.clone(),
                amount: bid_fee.into(),
            },
        )?],
    }));
}
if liquidator_fee.is_zero() {
    messages.push(CosmosMsg::Bank(BankMsg::Send {
        to_address: liquidator,
        amount: vec![deduct_tax(
            deps.as_ref(),
            Coin {
                denom: config.stable_denom.clone(),
                amount: liquidator_fee.into(),
            },
        )?],
    }));
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commits b3c5629 and d00ba8d.

8.12 LIQUIDATOR FEE IS NOT SENT

// High

Description

The execute_liquidation function in the krp-cdp-contracts/liquidation_queue and krp-market-contracts/liquidation_queue contracts is responsible for executing the liquidation of the debt using the previously deposited bids.

There is an error in the code at the time of sending the liquidator_fee: the zero validation is incorrect, resulting in a situation where the fee will only be sent if it is equal to zero. This validation should incorporate the ! symbol before the condition to make sense.

Code Location

Code fragment of the execute_liquidation function of the krp-market-contracts/liquidation_queue contract:

krp-market-contracts/contracts/liquidation_queue/src/bid.rs

let mut messages: Vec<CosmosMsg> = vec![CosmosMsg::Bank(BankMsg::Send {
    to_address: repay_address,
    amount: vec![deduct_tax(
        deps.as_ref(),
        Coin {
            denom: config.stable_denom.clone(),
            amount: repay_amount.into(),
        },
    )?],
})];

if bid_fee.is_zero() {
    messages.push(CosmosMsg::Bank(BankMsg::Send {
        to_address: fee_address,
        amount: vec![deduct_tax(
            deps.as_ref(),
            Coin {
                denom: config.stable_denom.clone(),
                amount: bid_fee.into(),
            },
        )?],
    }));
}
if liquidator_fee.is_zero() {
    messages.push(CosmosMsg::Bank(BankMsg::Send {
        to_address: liquidator,
        amount: vec![deduct_tax(
            deps.as_ref(),
            Coin {
                denom: config.stable_denom.clone(),
                amount: liquidator_fee.into(),
            },
        )?],
    }));
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commits b3c5629 and d00ba8d.

8.13 MISCALCULATION OF MAX LOAN TO VALUE WHEN QUERYING AVAILABLE COLLATERAL

// High

Description

In the query_collateral_available function in the krp-cdp-contracts/central_control contract, the value of max_loans_value is miscalculated, and its value is overwritten in each step of the for loop. As a consequence, the value returned when querying about the available collateral for users will be inaccurate.

Currently, the value of max_loans_value is:

\begin{math} max_loans_value = collateral.1 * price_resp.emv_price * collateral_info.max_ltv \end{math}

However, it should be:

\begin{math} max_loans_value += collateral.1 * price_resp.emv_price * collateral_info.max_ltv \end{math}

Code Location

krp-cdp-contracts/contracts/central_control/src/contract.rs

for collateral in collaterals {
 let collateral_info = read_whitelist_elem(deps.storage, &collateral.0)?;
 let price_resp = query_price(
  deps,
  deps.api.addr_humanize(&config.oracle_contract)?,
  deps.api.addr_humanize(&collateral.0)?.to_string(),
  "".to_string(),
  None,
 )?;
 if collateral.0 == collateral_raw {
  collateral_amount = collateral.1;
  collateral_price = price_resp.emv_price;
  collateral_max_ltv = collateral_info.max_ltv;
 } else {
  max_loans_value = collateral.1 * price_resp.emv_price * collateral_info.max_ltv;
 }
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 0950837.

8.14 REWARDS BONDING FUNCTIONALITY IS UNAVAILABLE

// High

Description

Executing the ExecuteMsg::BondRewards message in krp-staking-contracts/basset_sei_hub contract is restricted for only the krp-staking-contracts/basset_sei_rewards_dispatcher contract. However, there is no function in that contract that allows to execute the bond, which makes the rewards bonding functionality unavailable.

Code Location

The krp-staking-contracts/basset_sei_rewards_dispatcher contract does not call the ExecuteMsg::BondRewards message:

krp-staking-contracts/contracts/basset_sei_rewards_dispatcher/src/contract.rs

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
 match msg {
  ExecuteMsg::SwapToRewardDenom {
   bsei_total_bonded: bsei_total_mint_amount,
   stsei_total_bonded: stsei_total_mint_amount,
  } => execute_swap(
   deps,
   env,
   info,
   bsei_total_mint_amount,
   stsei_total_mint_amount,
  ),
  ExecuteMsg::DispatchRewards {} => execute_dispatch_rewards(deps, env, info),
  ExecuteMsg::UpdateConfig {
   owner,
   hub_contract,
   bsei_reward_contract,
   stsei_reward_denom,
   bsei_reward_denom,
   krp_keeper_address,
   krp_keeper_rate,
  } => execute_update_config(
   deps,
   env,
   info,
   owner,
   hub_contract,
   bsei_reward_contract,
   stsei_reward_denom,
   bsei_reward_denom,
   krp_keeper_address,
   krp_keeper_rate,
  ),
  ExecuteMsg::UpdateSwapContract { swap_contract } => {
   update_swap_contract(deps, info, swap_contract)
  }
  ExecuteMsg::UpdateSwapDenom { swap_denom, is_add } => {
   update_swap_denom(deps, info, swap_denom, is_add)
  }
  ExecuteMsg::UpdateOracleContract { oracle_contract } => {
   update_oracle_contract(deps, info, oracle_contract)
  }
 }
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit a682bc4.

8.15 WITHOUT POSSIBILITY TO SWAP ALL NATIVE TOKENS TO REWARD DENOMINATION

// High

Description

Executing the ExecuteMsg::SwapToRewardDenom message in the krp-staking-contracts/basset_sei_reward contract is restricted for only the krp-staking-contracts/basset_sei_rewards_dispatcher contract. However, there is no function in that contract that allows to execute the swap, which makes not possible to swap all native tokens to reward denomination.

Code Location

The krp-staking-contracts/basset_sei_rewards_dispatcher contract does not call the ExecuteMsg::SwapToRewardDenom message:

krp-staking-contracts/contracts/basset_sei_rewards_dispatcher/src/contract.rs

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
 match msg {
  ExecuteMsg::SwapToRewardDenom {
   bsei_total_bonded: bsei_total_mint_amount,
   stsei_total_bonded: stsei_total_mint_amount,
  } => execute_swap(
   deps,
   env,
   info,
   bsei_total_mint_amount,
   stsei_total_mint_amount,
  ),
  ExecuteMsg::DispatchRewards {} => execute_dispatch_rewards(deps, env, info),
  ExecuteMsg::UpdateConfig {
   owner,
   hub_contract,
   bsei_reward_contract,
   stsei_reward_denom,
   bsei_reward_denom,
   krp_keeper_address,
   krp_keeper_rate,
  } => execute_update_config(
   deps,
   env,
   info,
   owner,
   hub_contract,
   bsei_reward_contract,
   stsei_reward_denom,
   bsei_reward_denom,
   krp_keeper_address,
   krp_keeper_rate,
  ),
  ExecuteMsg::UpdateSwapContract { swap_contract } => {
   update_swap_contract(deps, info, swap_contract)
  }
  ExecuteMsg::UpdateSwapDenom { swap_denom, is_add } => {
   update_swap_denom(deps, info, swap_denom, is_add)
  }
  ExecuteMsg::UpdateOracleContract { oracle_contract } => {
   update_oracle_contract(deps, info, oracle_contract)
  }
 }
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit a5c858c.

8.16 UNCHECKED BALANCE CHANGE COULD LEAD TO UNFAIR WITHDRAWALS

// High

Description

The process_withdraw_rate function in the krp-staking-contracts/basset_sei_hub contract is not validating that the value of hub_balance is greater than prev_hub_balance. As a consequence, some edge scenarios could lead to users receiving less than expected when withdrawing. The following scenarios could arise in this condition (non-exhaustive list):

  • Hub contract is migrated and the remaining balance is transferred to another address.
  • Unauthorized transfer of funds out of the hub.
  • Slashing of validators.

Code Location

krp-staking-contracts/contracts/basset_sei_hub/src/unbond.rs

let balance_change = SignedInt::from_subtraction(hub_balance, state.prev_hub_balance);
let actual_unbonded_amount = balance_change.0;

let mut bsei_unbond_ratio = Decimal256::zero();
if stsei_total_unbonded_amount + bsei_total_unbonded_amount > Uint256::zero() {
 let stsei_unbond_ratio = Decimal256::from_ratio(
  stsei_total_unbonded_amount.0,
  (stsei_total_unbonded_amount + bsei_total_unbonded_amount).0,
 );
 bsei_unbond_ratio = Decimal256::one() - stsei_unbond_ratio;
}

let bsei_actual_unbonded_amount = Uint256::from(actual_unbonded_amount) * bsei_unbond_ratio;
// Use signed integer in case of some rogue transfers.
let bsei_slashed_amount =
 SignedInt::from_subtraction(bsei_total_unbonded_amount, bsei_actual_unbonded_amount);
let stsei_slashed_amount = SignedInt::from_subtraction(
 stsei_total_unbonded_amount,
 Uint256::from(actual_unbonded_amount) - bsei_actual_unbonded_amount,
);
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 9c0d95a.

8.17 INADEQUATE TRACKING OF PENDING REDELEGATIONS

// Medium

Description

When executing the remove_validator function in the krp-staking-contracts/basset_sei_validators_registry contract and the redelegation was not possible (e.g.: because of can_redelegate.amount), the validator is removed from the storage, but there is not an adequate tracking of pending redelegations to be done later.

Code Location

krp-staking-contracts/contracts/basset_sei_validators_registry/src/contract.rs

if let Some(delegation) = delegated_amount {
 // Terra core returns zero if there is another active redelegation
 // That means we cannot start a new redelegation, so we only remove a validator from
 // the registry.
 // We'll do a redelegation manually later by sending RedelegateProxy to the hub
 if delegation.can_redelegate.amount < delegation.amount.amount {
  return StdResult::Ok(Response::new());
 }

 let (_, delegations) =
  calculate_delegations(delegation.amount.amount, validators.as_slice())?;
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit ab8ef52.

8.18 MAX NUMBER OF BID SLOTS COULD BE MODIFIED AFTER BIDS WERE SUBMITTED

// Medium

Description

The update_collateral_info function in the krp-cdp-contracts/liquidation_queue and krp-market-contracts/liquidation_queue contracts allows the owner to modify some parameters of the CollateralInfo struct at any time. One of these parameters is max_slot, the maximum number of slots in the liquidation queue in each collateral for future user bids.

If a user has submitted some bids in any of the last slots in the queue and the max_slot parameter is replaced with a smaller one, the bids will not be used in future liquidations because the for loop of the execute_liquidation function will not reach them. This operation can only be executed by the owner, but the code does not check if there are already bids submitted in the last positions before replacing the parameter.

It is worth mentioning that the bids are not lost, they could be refunded calling the retract_bid function, but it requires a spent of gas from the user and, if the user is not notified about the update, the bids would be stuck for an undetermined time, missing the opportunity to invest these bids in other liquidation queues.

Code Location

Code fragment of the update_collateral_info function in the krp-market-contracts/liquidation_queue contract:

krp-market-contracts/contracts/liquidation_queue/src/contract.rs

if let Some(bid_threshold) = bid_threshold {
    collateral_info.bid_threshold = bid_threshold;
}

if let Some(max_slot) = max_slot {
    // assert max slot does not exceed cap and max premium rate does not exceed 1
    assert_max_slot(max_slot)?;
    assert_max_slot_premium(max_slot, collateral_info.premium_rate_per_slot)?;
    collateral_info.max_slot = max_slot;
}

// save collateral info
store_collateral_info(deps.storage, &collateral_token_raw, &collateral_info)?;

Ok(Response::new().add_attribute("action", "update_collateral_info"))
}
BVSS
Recommendation

RISK ACCEPTED: The Kryptonite team accepted the risk of this finding.

8.19 SOME FUNCTIONS RECEIVE MULTIPLE NATIVE COINS INSTEAD OF ONE

// Medium

Description

Some functions in the krp-basset-convert/krp_basset_converter and swap-extension/swap_sparrow contracts can receive multiple native coins, which would allow a double payment if users mistakenly send native coins different from the target one. The affected functions are the following:

  • krp-basset-convert/krp_basset_converter: execute_convert_to_basset function
  • swap-extension/swap_sparrow: swap_denom function

Code Location

Code fragment of the execute_convert_to_basset function in the krp-basset-convert/krp_basset_converter contract:

krp-basset-convert/contracts/krp_basset_converter/src/contract.rs

let coin_denom = config.native_denom.unwrap();

let coin = info
    .funds
    .iter()
    .find(|x| x.denom == coin_denom && x.amount > Uint128::zero())
    .ok_or_else(|| {
      StdError::generic_err(format!("No {} assets are provided to deposit", coin_denom));
    });

let denom_decimals = 6u8;
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commits a840e64 and d089b97.

8.20 REDELEGATION IS NOT RESTRICTED TO ACTIVE VALIDATORS

// Medium

Description

The execute_redelegate_proxy function in the krp-staking-contracts/basset_sei_hub contract does not restrict that redelegation is done only to active validators, which could create unexpected situations. For example, if the owner mistakenly delegates to a non-active validator and wants to fix it by redelegating again to an active validator, he will need to wait a cooldown period (because of consecutive redelegations) to carry out this task.

Code Location

krp-staking-contracts/contracts/basset_sei_hub/src/contract.rs

if sender_contract_addr != validators_registry_contract && sender_contract_addr != conf.creator
{
 return Err(StdError::generic_err("unauthorized"));
}

let messages: Vec<CosmosMsg> = redelegations
 .into_iter()
 .map(|(dst_validator, amount)| {
  cosmwasm_std::CosmosMsg::Staking(StakingMsg::Redelegate {
                src_validator: src_validator.clone(),
                dst_validator,
                amount,
  })
 })
 .collect();
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 7de9f39.

8.21 COIN DENOMINATION IS NOT CHECKED WHEN REMOVING VALIDATORS

// Medium

Description

When removing a validator using the remove_validator function in the krp-staking-contracts/basset_sei_validators_registry contract, there is no check about coin denomination in the delegated amount (i.e.: coin to be redelegated). As a consequence, some unexpected situations could arise in the protocol, e.g.: removing a validator with delegated native coins different from the target one.

Code Location

There is no check about coin denomination in the remove_validator function:

krp-staking-contracts/contracts/basset_sei_validators_registry/src/contract.rs

for i in 0..delegations.len() {
 if delegations[i].is_zero() {
  continue;
 }
 redelegations.push((
  validators[i].address.clone(),
  Coin::new(delegations[i].u128(), delegation.amount.denom.as_str()),
 ));
}
BVSS
Recommendation

RISK ACCEPTED: The Kryptonite team accepted the risk of this finding.

8.22 INADEQUATE TRACKING OF SWAPPED AMOUNTS

// Medium

Description

The swap_denom function in the swap-extension/swap_sparrow contract updates the values of swap_info.total_amount_in and swap_info.total_amount_out variables whenever there is a swap operation. However, those values have a mix of both coins involved in the swap, so after some operations, it won't be possible to identify how much comes from each one of the coins.

Code Location

swap-extension/contracts/swap_sparrow/src/handler.rs

// swap
let asset = Asset {
 amount: payment.amount,
 info: asset_infos[0].clone(),
};
let pair_address = pair_config.pair_address.clone();
let offer_asset = asset.clone();
let simulation_response = query_simulation(&deps.querier, pair_address.clone().to_string(), offer_asset.clone())?;

swap_info.total_amount_out += simulation_response.return_amount;
swap_info.total_amount_in += payment.amount;
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 22e954a.

8.23 UNBOND WAIT LISTS CAN BE MIGRATED EVEN IF THE CONTRACT IS NOT PAUSED

// Low

Description

The migrate_unbond_wait_lists function in the krp-staking-contracts/basset_sei_hub contract allows migrating the unbond wait lists without previously verifying whether the contract is paused or not, which could break the initial assumption about the current state of the contract and would arise unexpected situations, e.g.: reverting unbonding requests.

Code Location

The migrate_unbond_wait_lists function assumes that the contract is pause without verifying it:

krp-staking-contracts/contracts/basset_sei_hub/src/state.rs

pub fn migrate_unbond_wait_lists(
 storage: &mut dyn Storage,
 limit: Option<u32>,
) -> StdResult<Response> {
 let (removed_keys, num_migrated_entries) = {
  let old_unbond_wait_list_entries = read_old_unbond_wait_lists(storage, limit)?;
  if old_unbond_wait_list_entries.is_empty() {
   return Ok(Response::new().add_attributes(vec![
    attr("action", "migrate_unbond_wait_lists"),
    attr("num_migrated_entries", "0"),
   ]));
  }

  let mut num_migrated_entries: u32 = 0;
  let mut new_unbond_wait_list: Bucket<UnbondWaitEntity> =
   Bucket::multilevel(storage, &[NEW_PREFIX_WAIT_MAP]);
  let mut removed_keys: Vec<Vec<u8>> = vec![];

  for res in old_unbond_wait_list_entries {
   let (key, amount) = res?;
   let unbond_wait_entity = UnbondWaitEntity {
    bsei_amount: amount,
    stsei_amount: Uint128::zero(),
   };
   new_unbond_wait_list.save(&key, &unbond_wait_entity)?;
   removed_keys.push(key);
   num_migrated_entries += 1;
  }

  (removed_keys, num_migrated_entries)
 };

 let mut old_unbond_wait_list: Bucket<Uint128> =
 Bucket::multilevel(storage, &[OLD_PREFIX_WAIT_MAP]);
 for key in removed_keys {
  old_unbond_wait_list.remove(&key);
 }

 // unpause contract if we've migrated all unbond wait lists
 let old_unbond_wait_list_entries = read_old_unbond_wait_lists(storage, Some(1u32))?;
 if old_unbond_wait_list_entries.is_empty() {
  let mut params: Parameters = PARAMETERS.load(storage)?;
  params.paused = Some(false);
  PARAMETERS.save(storage, &params)?;
 }

 Ok(Response::new().add_attributes(vec![
  attr("action", "migrate_unbond_wait_lists"),
  attr("num_migrated_entries", num_migrated_entries.to_string()),
 ]))
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 37cb49a.

8.24 UNCHECKED MAX LOAN-TO-VALUE RATIO

// Low

Description

The whitelist_collateral function in the krp-cdp-contracts/central_control contract does not verify that max_ltv parameter is lower than 1. If it is mistakenly set to a value greater than 1, users will be able to redeem more coins than the value of their deposited collaterals.

This issue also applies to the register_whitelist and update_whitelist functions in the krp-market-contracts/overseer contract.

Code Location

The whitelist_collateral function in the krp-cdp-contracts/central_control contract:

krp-cdp-contracts/contracts/central_control/src/contract.rs

pub fn whitelist_collateral(
    deps: DepsMut,
    info: MessageInfo,
    name: String,
    symbol: String,
    max_ltv: Decimal256,
    custody_contract: CanonicalAddr,
    collateral_contract: CanonicalAddr,
    reward_book_contract: CanonicalAddr,
) -> Result<Response, ContractError> {
    let config = read_config(deps.storage)?;

    if deps.api.addr_canonicalize(info.sender.as_str())? != config.owner_addr {
        return Err(ContractError::Unauthorized(
            "whitelist_collateral".to_string(),
            info.sender.to_string(),
        ));
    }

    if max_ltv >= Decimal256::one() {
        return Err(ContractError::MaxLtvExceedsLimit {});
    }

    let data = WhitelistElem {
        name,
        symbol,
        max_ltv,
        custody_contract,
        collateral_contract: collateral_contract.clone(),
        reward_book_contract,
    };
    store_whitelist_elem(deps.storage, &collateral_contract, &data)?;
    Ok(Response::default())
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commits 13e9a4f and 8f2be6a.

8.25 FUNCTIONALITY TO UPDATE FEES IS NOT APPLIED CORRECTLY

// Low

Description

The assert_fees function in update_config from the krp-cdp-contracts/liquidation_queue and krp-market-contracts/liquidation_queue contracts is not applied correctly. As a consequence, some valid values will not be accepted, see example below:

Current values:

  • bid_fee = 0.3
  • liquidator_fee = 0.6

New values:

  • bid_fee = 0.5
  • liquidator_fee = 0.4

The new values will not be accepted despite that 0.5 + 0.4 < 1. The assert_fees function should be applied after trying to change both values.

Code Location

Snippet of update_config function in the krp-market-contracts/liquidation_queue contract:

krp-market-contracts/contracts/liquidation_queue/src/contract.rs

pub fn update_config(
    deps: DepsMut,
    info: MessageInfo,
    owner: Option<String>,
    oracle_contract: Option<String>,
    safe_ratio: Option<Decimal256>,
    bid_fee: Option<Decimal256>,
    liquidator_fee: Option<Decimal256>,
    liquidation_threshold: Option<Uint256>,
    price_timeframe: Option<u64>,
    waiting_period: Option<u64>,
    overseer: Option<String>,
) -> Result<Response, ContractError> {
    let mut config: Config = read_config(deps.storage)?;
    if deps.api.addr_canonicalize(info.sender.as_str())? != config.owner {
        return Err(ContractError::Unauthorized {});
    }

    if let Some(owner) = owner {
        config.owner = deps.api.addr_canonicalize(&owner)?;
    }

    if let Some(oracle_contract) = oracle_contract {
        config.oracle_contract = deps.api.addr_canonicalize(&oracle_contract)?;
    }

    if let Some(safe_ratio) = safe_ratio {
        config.safe_ratio = safe_ratio;
    }

    if let Some(bid_fee) = bid_fee {
        assert_fees(bid_fee + config.liquidator_fee)?;
        config.bid_fee = bid_fee;
    }

    if let Some(liquidator_fee) = liquidator_fee {
        assert_fees(liquidator_fee + config.bid_fee)?;
        config.liquidator_fee = liquidator_fee;
    }
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commits 5530267 and da2a2a3.

8.26 ARBITRARY MESSAGES CAN BE EXECUTED ON BEHALF OF THE HUB

// Low

Description

The execute_update_global function in the krp-staking-contracts/basset_sei_hub contract allows any user to execute arbitrary messages (airdrop_hooks) in the airdrop_registry_contract contract (out-of-scope for this assessment) on behalf of basset_sei_hub, which could arise unexpected situations in the protocol, like unauthorized changes.

Code Location

krp-staking-contracts/contracts/basset_sei_hub/src/contract.rs

if airdrop_hooks.is_some() {
 let registry_addr =
  deps.api
  .addr_humanize(&config.airdrop_registry_contract.ok_or_else(|| {
   StdError::generic_err("the airdrop registry contract must have been registered")
  })?)?;
 for msg in airdrop_hooks.unwrap() {
  messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
   contract_addr: registry_addr.to_string(),
   msg,
   funds: vec![],
  }))
 }
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 31b6d03.

8.27 UNRELIABLE SOURCE OF RANDOMNESS

// Low

Description

The get_winning function in the krp-token-contracts/treasure contract uses the following parameters as a source of randomness to determine the winning numbers when pre-minting NFTs:

  • Block time
  • Block height
  • Position of the transaction in the block

Although those parameters are volatile and make it harder to guess the winning numbers, they are not a reliable source of randomness from a security perspective.

Code Location

krp-token-contracts/contracts/treasure/src/handler.rs

let mut win_nft_num = 0u64;
let mut lost_nft_num = 0u64;
let record_id = generate_next_global_id(deps.storage)?;
let winning_num = &config.winning_num;
let mod_num = &config.mod_num;
for i in 0..mint_num {
 let unique_factor = record_id + i;
 let winning = get_winning(
  env.clone(),
  unique_factor.to_string(),
  vec![],
  winning_num,
  mod_num,
 )?;
 if winning {
  win_nft_num += 1;
 } else {
  lost_nft_num += 1;
 }
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 9d12155.

8.28 WITHDRAWAL COULD GET STUCK IF THERE ARE NO MORE UNBONDINGS

// Low

Description

If users unbond before the epoch period using the execute_unbond or execute_unbond_stsei functions in the krp-staking-contracts/basset_sei_hub contract, they won't be able to withdraw their tokens later unless someone else unbonds after the epoch period and triggers the undelegation. As a consequence, in an edge scenario, the withdrawal could get stuck.

Code Location

Relevant code fragments from the execute_unbond and execute_unbond_stsei functions:

krp-staking-contracts/contracts/basset_sei_hub/src/unbond.rs

// If the epoch period is passed, the undelegate message would be sent.
if passed_time > epoch_period {
 let mut undelegate_msgs =
  process_undelegations(&mut deps, env, &mut current_batch, &mut state)?;
 messages.append(&mut undelegate_msgs);
}

krp-staking-contracts/contracts/basset_sei_hub/src/unbond.rs

// If the epoch period is passed, the undelegate message would be sent.
if passed_time > epoch_period {
 let mut undelegate_msgs =
  process_undelegations(&mut deps, env, &mut current_batch, &mut state)?;
 messages.append(&mut undelegate_msgs);
}
BVSS
Recommendation

RISK ACCEPTED: The Kryptonite team accepted the risk of this finding.

8.29 OWNERSHIP CAN BE TRANSFERRED WITHOUT CONFIRMATION

// Low

Description

An incorrect use of the execute_update_config, update_config, change_owner, change_gov or update_staking_config functions in some contracts can set owner to an invalid address and inadvertently lose control of them, which cannot be undone in any way.

The affected contracts are the following:

  • krp-staking-contracts/basset_sei_hub
  • krp-staking-contracts/basset_sei_rewards_dispatcher
  • krp-staking-contracts/basset_sei_validators_registry
  • krp-cdp-contracts/central_control
  • krp-cdp-contracts/custody
  • krp-cdp-contracts/liquidation_queue
  • krp-cdp-contracts/reward_book
  • krp-cdp-contracts/stable_pool
  • krp-market-contracts/custody_base
  • krp-market-contracts/custody_bsei
  • krp-market-contracts/distribution_model
  • krp-market-contracts/interest_model
  • krp-market-contracts/liquidation_queue
  • krp-market-contracts/market
  • krp-market-contracts/overseer
  • krp-oracle/oracle_pyth
  • krp-token-contracts/boost
  • krp-token-contracts/dispatcher
  • krp-token-contracts/distribute
  • krp-token-contracts/fund
  • krp-token-contracts/keeper
  • krp-token-contracts/seilor
  • krp-token-contracts/staking
  • krp-token-contracts/ve_seilor
  • swap-extension/swap_sparrow

Code Location

Example - Code of the update_config function in the krp-market-contracts/custody_base contract:

krp-market-contracts/contracts/custody_base/src/contract.rs

pub fn update_config(
    deps: DepsMut,
    info: MessageInfo,
    owner: Option<Addr>,
    liquidation_contract: Option<Addr>,
) -> Result<Response, ContractError> {
    let mut config: Config = read_config(deps.storage)?;

    if deps.api.addr_canonicalize(info.sender.as_str())? != config.owner {
        return Err(ContractError::Unauthorized {});
    }

    if let Some(owner) = owner {
        config.owner = deps.api.addr_canonicalize(owner.as_str())?;
    }

    if let Some(liquidation_contract) = liquidation_contract {
        config.liquidation_contract = deps.api.addr_canonicalize(liquidation_contract.as_str())?;
    }

    store_config(deps.storage, &config)?;
    Ok(Response::new().add_attributes(vec![attr("action", "update_config")]))
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commits 2a7389f, 93247af, a1d8a9c, f3c96c8, 5ab2069, 2407815 and 6dec8f1.

8.30 COMMENTED TRANSACTION MESSAGE

// Low

Description

The claim_rewards function in the krp-market-contracts/market contract could be called by any user in order to get back the rewards generated by their deposits. Currently, this operation will not work since the message which contains the information on the transaction is commented, so a blank message will be executed.

Code Location

Code fragment of the claim_rewards function in the krp-market-contracts/market contract:

krp-market-contracts/contracts/market/src/borrow.rs

    let claim_amount = liability.pending_rewards * Uint256::one();
    liability.pending_rewards = liability.pending_rewards - Decimal256::from_uint256(claim_amount);

    store_state(deps.storage, &state)?;
    store_borrower_info(deps.storage, &borrower_raw, &liability)?;
    let messages: Vec<CosmosMsg> = vec![];
    // let messages: Vec<CosmosMsg> = if !claim_amount.is_zero() {
    //     vec![CosmosMsg::Wasm(WasmMsg::Execute {
    //         contract_addr: deps
    //             .api
    //             .addr_humanize(&config.distributor_contract)?
    //             .to_string(),
    //         funds: vec![],
    //         msg: to_binary(&FaucetExecuteMsg::Spend {
    //             recipient: if let Some(to) = to {
    //                 to.to_string()
    //             } else {
    //                 borrower.to_string()
    //             },
    //             amount: claim_amount.into(),
    //         })?,
    //     })]
    // } else {
    //     vec![]
    // };

    Ok(Response::new().add_messages(messages).add_attributes(vec![
        attr("action", "claim_rewards"),
        attr("claim_amount", claim_amount),
    ]))
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commits e766b7c.

8.31 UNCHECKED REDEEM FEE

// Low

Description

The instantiate and update_config functions in the krp-cdp-contracts/central_control contract do not verify that redeem_fee is lower than 1. If it is mistakenly set to a value greater than 1, the operation of redeeming stable coins will always panic because of an underflow error.

Code Location

The instantiate and update_config functions do not verify that redeem_fee is lower than 1:

krp-cdp-contracts/contracts/central_control/src/contract.rs

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
 deps: DepsMut,
 _env: Env,
 _info: MessageInfo,
 msg: InstantiateMsg,
) -> Result<Response, ContractError> {
 let api = deps.api;
 let config = Config {
  owner_addr: api.addr_canonicalize(&msg.owner_addr.as_str())?,
  oracle_contract: api.addr_canonicalize(&msg.oracle_contract.as_str())?,
  pool_contract: api.addr_canonicalize(&msg.pool_contract.as_str())?,
  liquidation_contract: api.addr_canonicalize(&msg.liquidation_contract.as_str())?,
  epoch_period: msg.epoch_period,
  redeem_fee: msg.redeem_fee,
  stable_denom: msg.stable_denom,
 };
 store_config(deps.storage, &config)?;

 Ok(Response::default())
}

krp-cdp-contracts/contracts/central_control/src/contract.rs

if let Some(epoch_period) = epoch_period {
 config.epoch_period = epoch_period;
}

if let Some(redeem_fee) = redeem_fee {
 config.redeem_fee = redeem_fee;
}

store_config(deps.storage, &config)?;
Ok(Response::default())
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 54b01dd.

8.32 UNCHECKED KEEPER RATE

// Low

Description

The instantiate and execute_update_config functions in the krp-staking-contracts/basset_sei_rewards_dispatcher contract do not verify that krp_keeper_rate is lower than 1. If it is mistakenly set to a value greater than 1, the operation of rewards dispatching will always panic because of an underflow error.

Code Location

The instantiate and execute_update_config functions do not verify that krp_keeper_rate is lower than 1:

krp-staking-contracts/contracts/basset_sei_rewards_dispatcher/src/contract.rs

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
 deps: DepsMut,
 _env: Env,
 info: MessageInfo,
 msg: InstantiateMsg,
) -> StdResult<Response> {
 let conf = Config {
  owner: deps.api.addr_canonicalize(info.sender.as_str())?,
  hub_contract: deps.api.addr_canonicalize(&msg.hub_contract)?,
  bsei_reward_contract: deps.api.addr_canonicalize(&msg.bsei_reward_contract)?,
  bsei_reward_denom: msg.bsei_reward_denom,
  stsei_reward_denom: msg.stsei_reward_denom,
  krp_keeper_address: deps.api.addr_canonicalize(&msg.krp_keeper_address)?,
  krp_keeper_rate: msg.krp_keeper_rate,
  swap_contract: deps.api.addr_canonicalize(&msg.swap_contract)?,
  swap_denoms: msg.swap_denoms,
  oracle_contract: deps.api.addr_canonicalize(&msg.oracle_contract)?,
 };

 store_config(deps.storage, &conf)?;
 Ok(Response::default())
}

krp-staking-contracts/contracts/basset_sei_rewards_dispatcher/src/contract.rs

if let Some(_b) = bsei_reward_denom {
 CONFIG.update(deps.storage, |mut last_config| -> StdResult<_> {
  last_config.bsei_reward_denom = _b;
  Ok(last_config)
 })?;
}

if let Some(r) = krp_keeper_rate {
 CONFIG.update(deps.storage, |mut last_config| -> StdResult<_> {
  last_config.krp_keeper_rate = r;
  Ok(last_config)
 })?;
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit c20f645.

8.33 MAXIMUM AMOUNT OF TOKENS TO MINT IS NOT VALIDATED

// Low

Description

The update_config function in the krp-token-contracts/ve_seilor contract does not verify that max_minted should be greater than total_minted. If this parameter is mistakenly set (i.e.: lower than total_minted), it will be possible to mint new ve_seilor tokens.

Code Location

The update_config function does not verify that max_minted should be greater than total_minted:

krp-token-contracts/contracts/ve_seilor/src/handler.rs

pub fn update_config(deps: DepsMut, info: MessageInfo, max_minted: Option<Uint128>, fund: Option<Addr>, gov: Option<Addr>) -> Result<Response, ContractError> {
 let mut vote_config = read_vote_config(deps.storage)?;

 if info.sender != vote_config.gov {
  return Err(ContractError::Unauthorized {});
 }

 let mut attrs = vec![attr("action", "update_config"), attr("sender", info.sender.to_string())];
 if let Some(max_minted) = max_minted {
  vote_config.max_minted = max_minted.clone();
  attrs.push(attr("max_minted", max_minted.to_string()));
 }
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 6b82713.

8.34 UNCHECKED PARAMETERS IN DISPATCHER CONTRACT

// Low

Description

The instantiate or update_config functions in the krp-token-contracts/dispatcher contract do not verify the following parameters:

  • start_lock_period_time >= current block time
  • duration_per_period > 0
  • periods > 0

If one of those parameters is mistakenly set, it could arise in unexpected situations, e.g.: panicking when users try to claim.

Code Location

The instantiate or update_config functions do not verify the appropriate boundaries:

krp-token-contracts/contracts/dispatcher/src/contract.rs

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
 deps: DepsMut,
 _env: Env,
 info: MessageInfo,
 msg: InstantiateMsg,
) -> StdResult<Response> {
 let gov = msg.gov.unwrap_or_else(|| info.sender.clone());

 let global_config = GlobalConfig {
  gov,
  claim_token: msg.claim_token,
  total_lock_amount: msg.total_lock_amount,
  start_lock_period_time: msg.start_lock_period_time,
  duration_per_period: msg.duration_per_period,
  periods: msg.periods,
 };

 let global_state = GlobalState {
  total_user_lock_amount: Uint256::zero(),
  total_user_claimed_lock_amount: Uint256::zero(),
 };

 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
 store_global_config(deps.storage, &global_config)?;
 store_global_state(deps.storage, &global_state)?;

 Ok(Response::new().add_attribute("action", "instantiate"))
}

krp-token-contracts/contracts/dispatcher/src/handler.rs

if let Some(start_lock_period_time) = msg.start_lock_period_time {
 // check current block time > start_lock_period_time
 if config.start_lock_period_time < env.block.time.seconds() {
  return Err(ContractError::InvalidStartLockPeriodTime {});
 }
 config.start_lock_period_time = start_lock_period_time.clone();
 attrs.push(attr(
  "start_lock_period_time",
  start_lock_period_time.to_string(),
 ));
}

store_global_config(deps.storage, &config)?;

Ok(Response::default().add_attributes(attrs))
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commits 183833f, 8e08417 and a48bf9e.

8.35 UNCHECKED PARAMETERS IN TREASURE CONTRACT

// Low

Description

The instantiate or update_config functions in the krp-token-contracts/treasure contract do not verify the following parameters:

  • start_lock_time >= current block time
  • end_lock_time > start_lock_time
  • nft_start_pre_mint_time >= current block time
  • nft_start_pre_mint_time > end_lock_time
  • nft_end_pre_mint_time > nft_start_pre_mint_time

If one of those parameters is mistakenly set, it could arise unexpected situations, e.g.: users couldn't withdraw rewards or pre-mint NFTs.

Code Location

The instantiate or update_config functions do not verify the appropriate boundaries:

krp-token-contracts/contracts/treasure/src/contract.rs

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
 deps: DepsMut,
 _env: Env,
 info: MessageInfo,
 msg: InstantiateMsg,
) -> StdResult<Response> {
 let sender = info.clone().sender;
 let gov = msg.gov.unwrap_or(sender.clone());

 let config = TreasureConfig {
  gov: gov.clone(),
  lock_token: msg.lock_token.clone(),
  start_lock_time: msg.start_lock_time,
  end_lock_time: msg.end_lock_time,
  dust_reward_per_second: msg.dust_reward_per_second,
  withdraw_delay_duration: msg.withdraw_delay_duration,
  winning_num: msg.winning_num,
  mod_num: msg.mod_num,
  punish_receiver: msg.punish_receiver,
  nft_start_pre_mint_time: msg.nft_start_pre_mint_time,
  nft_end_pre_mint_time: msg.nft_end_pre_mint_time,
  no_delay_punish_coefficient: msg.no_delay_punish_coefficient,
  mint_nft_cost_dust: msg.mint_nft_cost_dust,
 };

krp-token-contracts/contracts/treasure/src/handler.rs

if let Some(lock_token) = config_msg.lock_token {
 deps.api.addr_validate(lock_token.clone().as_str())?;
 config.lock_token = lock_token.clone();
 attrs.push(attr("lock_token", lock_token.to_string()));
}
if let Some(start_lock_time) = config_msg.start_lock_time {
 config.start_lock_time = start_lock_time.clone();
 attrs.push(attr("start_lock_time", start_lock_time.to_string()));
}
if let Some(end_lock_time) = config_msg.end_lock_time {
 config.end_lock_time = end_lock_time.clone();
 attrs.push(attr("end_lock_time", end_lock_time.to_string()));
}

krp-token-contracts/contracts/treasure/src/handler.rs

if let Some(punish_receiver) = config_msg.punish_receiver {
 deps.api.addr_validate(punish_receiver.clone().as_str())?;
 config.punish_receiver = punish_receiver.clone();
 attrs.push(attr("punish_receiver", punish_receiver.to_string()));
}

if let Some(nft_start_pre_mint_time) = config_msg.nft_start_pre_mint_time {
 config.nft_start_pre_mint_time = nft_start_pre_mint_time.clone();
 attrs.push(attr(
  "nft_start_pre_mint_time",
  nft_start_pre_mint_time.to_string(),
 ));
}
if let Some(nft_end_pre_mint_time) = config_msg.nft_end_pre_mint_time {
 config.nft_end_pre_mint_time = nft_end_pre_mint_time.clone();
 attrs.push(attr(
  "nft_end_pre_mint_time",
  nft_end_pre_mint_time.to_string(),
 ));
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 8776456.

8.36 DURATION IS NOT VALIDATED IN STAKING CONTRACT

// Low

Description

The instantiate and update_staking_duration functions in krp-token-contracts/staking contract do not validate that the duration is greater than 0. If it is mistakenly set to 0, the operation of notifying reward amount will always panic because of a division by 0.

Code Location

The instantiate and update_staking_duration functions do not verify that duration is greater than 0:

krp-token-contracts/contracts/staking/src/constract.rs

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
 deps: DepsMut,
 _env: Env,
 info: MessageInfo,
 msg: InstantiateMsg,
) -> Result<Response, ContractError> {
 let gov = msg.gov.unwrap_or_else(|| info.sender.clone());

 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

 let staking_config = StakingConfig {
  gov,
  staking_token: msg.staking_token,
  rewards_token: msg.rewards_token,
  boost: msg.boost,
  fund: msg.fund,
  reward_controller_addr: msg.reward_controller_addr,
 };

 store_staking_config(deps.storage, &staking_config)?;

 let staking_state = StakingState {
  duration: msg.duration,
  finish_at: Uint128::zero(),
  updated_at: Uint128::zero(),
  reward_rate: Uint256::zero(),
  reward_per_token_stored: Uint128::zero(),
  total_supply: Uint128::zero(),
 };

krp-token-contracts/contracts/staking/src/handler.rs

pub fn update_staking_duration(
 deps: DepsMut,
 env: Env,
 info: MessageInfo,
 duration: Uint128,
) -> Result<Response, ContractError> {
 let staking_config = read_staking_config(deps.storage)?;
 let mut staking_state = read_staking_state(deps.storage)?;
 if info.sender.ne(&staking_config.gov) {
  return Err(ContractError::Unauthorized {});
 }

 let current_time = Uint128::from(env.block.time.seconds());
 if staking_state.finish_at > current_time {
  return Err(ContractError::Std(StdError::generic_err(
   "duration can only be updated after the end of the current period",
  )));
 }
 staking_state.duration = duration.clone();

 store_staking_state(deps.storage, &staking_state)?; // update state
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 9def1db.

8.37 CLAIMABLE TIME IS NOT VALIDATED IN FUND CONTRACT

// Low

Description

The instantiate and update_fund_config functions in the krp-token-contracts/fund contract do not validate that the claim_able_time is greater than the current block time. If it is mistakenly set (i.e.: lower than current block time), users won't be able to unstake tokens.

Code Location

The instantiate and update_fund_config functions do not verify that claim_able_time is greater than the current block time:

krp-token-contracts/contracts/fund/src/contract.rs

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
 deps: DepsMut,
 _env: Env,
 info: MessageInfo,
 msg: InstantiateMsg,
) -> StdResult<Response> {
 set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

 let gov = msg.gov.unwrap_or_else(|| info.sender.clone());

 let config = FundConfig {
  gov,
  ve_seilor_addr: msg.ve_seilor_addr,
  seilor_addr: msg.seilor_addr,
  kusd_denom: msg.kusd_denom,
  kusd_reward_addr: msg.kusd_reward_addr,
  kusd_reward_total_amount: Uint128::zero(),
  kusd_reward_total_paid_amount: Uint128::zero(),
  reward_per_token_stored: Uint128::zero(),
  exit_cycle: msg.exit_cycle,
  claim_able_time: msg.claim_able_time,
 };

krp-token-contracts/contracts/fund/src/handler.rs

if let Some(claim_able_time) = msg.claim_able_time {
 config.claim_able_time = claim_able_time.clone();
 attrs.push(attr("claim_able_time", claim_able_time.to_string()));
}
store_fund_config(deps.storage, &config)?;
Ok(Response::new().add_attributes(attrs))
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit d17a174.

8.38 EXCHANGE RATE COULD INCREASE INDEFINITELY

// Low

Description

The values of bsei_exchange_rate and stsei_exchange_rate can increase indefinitely (i.e.: greater than 1) by directly burning bsei and stsei tokens, respectively. Although this issue is not immediately exploitable, if those exchange rates get increased too much eventually, it could create some overflow situations in the protocol when bonding, unbonding, withdrawing or converting.

Code Location

The execute_burn function in the basset_sei_token_bsei and basset_sei_token_stsei contracts is not adequately restricted:

krp-staking-contracts/contracts/basset_sei_token_bsei/src/handler.rs

pub fn execute_burn(
 deps: DepsMut,
 env: Env,
 info: MessageInfo,
 amount: Uint128,
) -> Result<Response, ContractError> {
 let sender = info.sender.clone();
 let reward_contract = query_reward_contract(&deps)?;
 let hub_contract = deps.api.addr_humanize(&read_hub_contract(deps.storage)?)?;

 let res: Response = cw20_burn(deps, env, info, amount)?;

krp-staking-contracts/contracts/basset_sei_token_stsei/src/handler.rs

pub fn execute_burn(
 deps: DepsMut,
 env: Env,
 info: MessageInfo,
 amount: Uint128,
) -> Result<Response, ContractError> {
 let hub_contract = deps.api.addr_humanize(&HUB_CONTRACT.load(deps.storage)?)?;

 let mut messages = vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute {
  contract_addr: hub_contract.to_string(),
  msg: to_binary(&CheckSlashing {})?,
  funds: vec![],
 }))];
 if info.sender != hub_contract {
  messages.push(SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute {
   contract_addr: hub_contract.to_string(),
   msg: to_binary(&CheckSlashing {})?,
   funds: vec![],
  })))
 }
 let res = cw20_burn(deps, env, info, amount)?;
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 67cc445.

8.39 PAIR KEY CAN CONTAIN DUPLICATED ASSETS

// Low

Description

The update_pair_config function in the swap-extension/swap_sparrow contract does not verify that asset_infos contains different assets, which could allow that the owner mistakenly registers an invalid pair of assets in the contract.

Code Location

The update_pair_config function does not verify that asset_infos contains different assets:

swap-extension/contracts/swap_sparrow/src/handler.rs

#[allow(clippy::too_many_arguments)]
pub fn update_pair_config(deps: DepsMut, info: MessageInfo,
 asset_infos: [AssetInfo; 2],
 pair_address: Addr, max_spread: Option<Decimal>,
 to: Option<Addr>) -> Result<Response, ContractError> {
 let config = read_config(deps.storage)?;
 if info.sender != config.owner {
  return Err(ContractError::Unauthorized {});
 }

 let mut pair_config = PairConfig {
  pair_address: pair_address.clone(),
  is_disabled: false,
  max_spread: None,
  to: None,
 };

 if let Some(max_spread) = max_spread {
  pair_config.max_spread = Some(max_spread);
 }
 if let Some(to) = to {
  pair_config.to = Some(to);
 }

 let pair_key = pair_key(&asset_infos);
 store_pair_configs(deps.storage, &pair_key, &pair_config)?;

 Ok(Response::new().add_attributes(vec![
  ("action", "update_pair_config"),
  ("pair_address", pair_address.as_str()),
  ("max_spread", max_spread.unwrap_or_default().to_string().as_str()), ]))
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 3ea0429.

8.40 ASSETS COULD MISMATCH WITH THE ONES IN PAIR ADDRESS

// Low

Description

The update_pair_config function in swap_sparrow contract does not verify that the assets in pair_address are the same ones as in asset_infos, which could allow that the owner mistakenly registers an invalid pair of assets in the contract.

Code Location

The update_pair_config function does not verify that the assets in pair_address are the same ones as in asset_infos:

swap-extension/contracts/swap_sparrow/src/handler.rs

#[allow(clippy::too_many_arguments)]
pub fn update_pair_config(deps: DepsMut, info: MessageInfo,
 asset_infos: [AssetInfo; 2],
 pair_address: Addr, max_spread: Option<Decimal>,
 to: Option<Addr>) -> Result<Response, ContractError> {
 let config = read_config(deps.storage)?;
 if info.sender != config.owner {
  return Err(ContractError::Unauthorized {});
 }

 let mut pair_config = PairConfig {
  pair_address: pair_address.clone(),
  is_disabled: false,
  max_spread: None,
  to: None,
 };

 if let Some(max_spread) = max_spread {
  pair_config.max_spread = Some(max_spread);
 }
 if let Some(to) = to {
  pair_config.to = Some(to);
 }

 let pair_key = pair_key(&asset_infos);
 store_pair_configs(deps.storage, &pair_key, &pair_config)?;

 Ok(Response::new().add_attributes(vec![
  ("action", "update_pair_config"),
  ("pair_address", pair_address.as_str()),
  ("max_spread", max_spread.unwrap_or_default().to_string().as_str()), ]))
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit aa6fd58.

8.41 IMMUTABLE VARIABLES CAN BE CHANGED IN STAKING CONTRACT

// Informational

Description

The update_staking_config function in the krp-token-contracts/staking contract allows updating the values of staking_token and rewards_token variables, which should be immutable. As a consequence, some unexpected situations could arise in the protocol, e.g.: users that can't withdraw their staked tokens.

Code Location

krp-token-contracts/contracts/staking/src/handler.rs

if let Some(staking_token) = update_struct.staking_token {
 deps.api.addr_validate(staking_token.clone().as_str())?; // validate staking token address
 staking_config.staking_token = staking_token.clone();
 attrs.push(attr("staking_token", staking_token.to_string()));
}

if let Some(rewards_token) = update_struct.rewards_token {
 deps.api.addr_validate(rewards_token.clone().as_str())?; // validate rewards token address
 staking_config.rewards_token = rewards_token.clone();
 attrs.push(attr("rewards_token", rewards_token.to_string()));
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit c8afb00.

8.42 MARKETING INFO IS NOT VALIDATED AT INSTANTIATION

// Informational

Description

If marketing info is not included at instantiation time, the execute_update_marketing and execute_upload_logo functions will always return an Unauthorized error message because marketing is None. The affected contracts are the following:

  • krp-staking-contracts/basset_sei_token_stsei
  • krp-token-contracts/seilor
  • krp-token-contracts/ve_seilor

Code Location

The marketing parameter is optional when instantiating the krp-staking-contracts/basset_sei_token_stsei contract:

krp-staking-contracts/contracts/basset_sei_token_stsei/src/msg.rs

#[derive(Serialize, Deserialize, JsonSchema)]
pub struct TokenInitMsg {
 pub name: String,
 pub symbol: String,
 pub decimals: u8,
 pub initial_balances: Vec<Cw20Coin>,
 pub hub_contract: String,
 pub marketing: Option<InstantiateMarketingInfo>,
}

\color{black}\color{white}The marketing parameter is optional when instantiating the krp-token-contracts/seilor contract:

krp-token-contracts/contracts/seilor/src/contract.rs

if let Some(marketing) = cw20_instantiate_msg.marketing {
 cw20_instantiate_msg.marketing = Some(InstantiateMarketingInfo {
  project: marketing.project,
  description: marketing.description,
  logo: marketing.logo,
  marketing: Some(gov.to_string()),
 });
}

let ins_res = cw20_instantiate(deps.branch(), env, info, cw20_instantiate_msg);
if let Err(err) = ins_res {
 return Err(ContractError::Std(StdError::generic_err(err.to_string())));
}

\color{black}\color{white}The marketing parameter is optional when instantiating the krp-token-contracts/ve_seilor contract:

krp-token-contracts/contracts/ve_seilor/src/contract.rs

cw20_instantiate_msg.marketing = if let Some(marketing) = cw20_instantiate_msg.marketing {
 Some(InstantiateMarketingInfo {
  project: marketing.project,
  description: marketing.description,
  logo: marketing.logo,
  marketing: Option::from(gov.clone().to_string()),
 })
} else {
 None
};
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commits fb5272f and 060cbb5.

8.43 LOCK AMOUNT IN GLOBAL STATE IS NOT VALIDATED WHEN CLAIMING

// Informational

Description

The user_claim function in the krp-token-contracts/dispatcher contract does not validate that global_state.total_user_claimed_lock_amount is less or equal than global_state.total_user_lock_amount. This situation could generate a panic during the claiming operation under edge scenarios.

Code Location

The user_claim function only validates that claimed_lock_amount <= total_user_lock_amount for the user state, but not for the global state:

krp-token-contracts/contracts/dispatcher/src/handler.rs

// check claimable amount is not zero
if claimable_amount == Uint256::zero() {
 return Err(ContractError::UserClaimAmountIsZero(sender.clone()));
}

if user_state.claimed_lock_amount > user_state.total_user_lock_amount {
 return Err(ContractError::UserClaimLockAmountTooLarge(sender.clone()));
}

store_user_state(deps.storage, &sender, &user_state)?;
store_global_state(deps.storage, &global_state)?;
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 6ce1c7e.

8.44 UNCHECKED VALIDATOR ADDRESSES

// Informational

Description

The validator addresses in the krp-token-contracts/basset_sei_validators_registry contract are not validated by addr_validate before saving them to storage in the instantiate and add_validator functions. In edge scenarios, this situation would allow storing invalid addresses, which could generate that some operations are reverted.

Code Location

The instantiate function does not use addr_validate before saving the validator addresses to storage:

krp-staking-contracts/contracts/basset_sei_validators_registry/src/contract.rs

pub fn instantiate(
 deps: DepsMut,
 _env: Env,
 info: MessageInfo,
 msg: InstantiateMsg,
) -> StdResult<Response> {
 CONFIG.save(
  deps.storage,
  &Config {
   owner: deps.api.addr_canonicalize(info.sender.as_str())?,
   hub_contract: deps.api.addr_canonicalize(msg.hub_contract.as_str())?,
  },
 )?;

 for v in msg.registry {
  REGISTRY.save(deps.storage, v.address.as_str().as_bytes(), &v)?;
 }

 Ok(Response::default())

\color{black}\color{white}The add_validator function does not use addr_validate before saving the validator addresses to storage:

krp-staking-contracts/contracts/basset_sei_validators_registry/src/contract.rs

pub fn add_validator(
 deps: DepsMut,
 _env: Env,
 info: MessageInfo,
 validator: Validator,
) -> StdResult<Response> {
 let config = CONFIG.load(deps.storage)?;
 let owner_address = deps.api.addr_humanize(&config.owner)?;
 let hub_address = deps.api.addr_humanize(&config.hub_contract)?;
 if info.sender != owner_address && info.sender != hub_address {
  return Err(StdError::generic_err("unauthorized"));
 }

 REGISTRY.save(
  deps.storage,
  validator.address.as_str().as_bytes(),
  &validator,
 )?;
 Ok(Response::default())
}
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commit f3c1794.

8.45 UNCHECKED SAFE RATIO

// Informational

Description

The instantiate and update_config functions from the krp-cdp-contracts/liquidation_queue and krp-market-contracts/liquidation_queue contracts do not verify that safe_ratio is lower than 1.

If it is mistakenly set to a value greater than 1, some unexpected results could appear or even a panic when querying about the liquidation amount.

Code Location

Snippet of update_config function in the krp-market-contracts/liquidation_queue contract:

krp-market-contracts/contracts/liquidation_queue/src/contract.rs

pub fn update_config(
    deps: DepsMut,
    info: MessageInfo,
    owner: Option<String>,
    oracle_contract: Option<String>,
    safe_ratio: Option<Decimal256>,
    bid_fee: Option<Decimal256>,
    liquidator_fee: Option<Decimal256>,
    liquidation_threshold: Option<Uint256>,
    price_timeframe: Option<u64>,
    waiting_period: Option<u64>,
    overseer: Option<String>,
) -> Result<Response, ContractError> {
    let mut config: Config = read_config(deps.storage)?;
    if deps.api.addr_canonicalize(info.sender.as_str())? != config.owner {
        return Err(ContractError::Unauthorized {});
    }

    if let Some(owner) = owner {
        config.owner = deps.api.addr_canonicalize(&owner)?;
    }

    if let Some(oracle_contract) = oracle_contract {
        config.oracle_contract = deps.api.addr_canonicalize(&oracle_contract)?;
    }

    if let Some(safe_ratio) = safe_ratio {
        config.safe_ratio = safe_ratio;
    }
BVSS
Recommendation

SOLVED: The Kryptonite team solved this issue in commits a373e5b, 9ec168e and d225015.

8.46 REDUNDANT LOGIC

// Informational

Description

The execute_convert_to_basset and the execute_convert_to_native functions in the krp-basset-convert/krp_basset_converter contract contain redundant logic, which could mean a possible error inside the logical conditions or, more likely, that the code is redundant and should be simplified as part of the DRY (Don't Repeat Yourself) principle used as a best practice in software development to improve the maintainability of code during all phases of its lifecycle.

Code Location

Redundant logic in the execute_convert_to_basset function:

krp-basset-convert/contracts/krp_basset_converter/src/contract.rs

pub(crate) fn execute_convert_to_basset(
 deps: DepsMut,
 _env: Env,
 info: MessageInfo,
) -> StdResult<Response> {
 let config = read_config(deps.storage)?;

 if config.native_denom.is_none() || config.native_denom.is_none() {
  return Err(StdError::generic_err(
   "native denom must be registered first",
  ));
 }

\color{black}\color{white}Redundant logic in the execute_convert_to_native function:

krp-basset-convert/contracts/krp_basset_converter/src/contract.rs

pub(crate) fn execute_convert_to_native(
 deps: DepsMut,
 _env: Env,
 _info: MessageInfo,
 amount: Uint128,
 sender: String,
) -> StdResult<Response> {
 let config = read_config(deps.storage)?;
 if config.native_denom.is_none() || config.native_denom.is_none() {
  return Err(StdError::generic_err(
   "native or basset token must be registered first",
  ));
 }
Score
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 2bd0553.

8.47 REPEATED EXECUTION MESSAGES

// Informational

Description

The ExecuteMsg::MigrateUnbondWaitList and ExecuteMsg::UpdateParams messages are repeated in the execute function from krp-staking-contracts/basset_sei_hub contract. Although this situation doesn't pose a security risk, it's included in the report as part of the DRY (Don't Repeat Yourself) principle used as a best practice in software development to improve the maintainability of code during all phases of its lifecycle.

Code Location

The ExecuteMsg::MigrateUnbondWaitList and ExecuteMsg::UpdateParams messages are repeated in the execute function:

krp-staking-contracts/contracts/basset_sei_hub/src/contract.rs

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
 if let ExecuteMsg::MigrateUnbondWaitList { limit } = msg {
  return migrate_unbond_wait_lists(deps.storage, limit);
 }

 if let ExecuteMsg::UpdateParams {
  epoch_period,
  unbonding_period,
  peg_recovery_fee,
  er_threshold,
  paused,
 } = msg
 {
  return execute_update_params(
   deps,
   env,
   info,
   epoch_period,
   unbonding_period,
   peg_recovery_fee,
   er_threshold,
   paused,
  );
 }

krp-staking-contracts/contracts/basset_sei_hub/src/contract.rs

ExecuteMsg::WithdrawUnbonded {} => execute_withdraw_unbonded(deps, env, info),
ExecuteMsg::CheckSlashing {} => execute_slashing(deps, env),
ExecuteMsg::UpdateParams {
 epoch_period,
 unbonding_period,
 peg_recovery_fee,
 er_threshold,
 paused,
} => execute_update_params(
  deps,
  env,
  info,
  epoch_period,
  unbonding_period,
  peg_recovery_fee,
  er_threshold,
  paused,
 ),

krp-staking-contracts/contracts/basset_sei_hub/src/contract.rs

ExecuteMsg::RedelegateProxy {
 src_validator,
 redelegations,
} => execute_redelegate_proxy(deps, env, info, src_validator, redelegations),
ExecuteMsg::MigrateUnbondWaitList { limit: _ } => Err(StdError::generic_err("forbidden")),
Score
Recommendation

ACKNOWLEDGED: The Kryptonite team acknowledged this finding.

8.48 STAKING TOKEN CALLS CHECK SLASHING TWICE

// Informational

Description

The ExecuteMsg::Burn message sent to the krp-staking-contracts/basset_sei_token_stsei contract triggers CheckSlashing twice on the hub, if the sender of that message is not the hub contract. The second call is redundant and does simply recompute the exchange rates another time, which leads to computational and gas waste.

Code Location

krp-staking-contracts/contracts/basset_sei_token_stsei/src/handler.rs

let mut messages = vec![SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute {
    contract_addr: hub_contract.to_string(),
    msg: to_binary(&CheckSlashing {})?,
    funds: vec![],
}))];
if info.sender != hub_contract {
    messages.push(SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute {
        contract_addr: hub_contract.to_string(),
        msg: to_binary(&CheckSlashing {})?,
        funds: vec![],
    })))
}
Score
Recommendation

SOLVED: The Kryptonite team solved this issue in commit e7c4805.

8.49 UNIMPLEMENTED MESSAGE

// Informational

Description

The QueryMsg::GetBufferedRewards message is not implemented in the krp-staking-contracts/basset_sei_rewards_dispatcher contract and could generate a panic if it is called. Although it is a minor issue, it is advisable to fix the logic in a production environment contract.

Code Location

krp-staking-contracts/contracts/basset_sei_rewards_dispatcher/src/contract.rs

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
 match msg {
  QueryMsg::Config {} => to_binary(&query_config(deps)?),
  QueryMsg::GetBufferedRewards {} => unimplemented!(),
 }
}
Score
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 3debcf9.

8.50 UNUSED MESSAGES

// Informational

Description

Along the codebase, there are some messages that are unused. This situation is not security-related, but mentioned in the report as part of the best practices in software development to improve the readability of code during all phases of its lifecycle. The affected messages are the following:

  • krp-cdp-contracts/liquidation_queue: ExecuteMsg::ExecuteBid
  • krp-token-contracts/ve_seilor: QueryMsg::GetPastTotalSupply

Code Location

The ExecuteMsg::ExecuteBid message is not used:

krp-cdp-contracts/contracts/liquidation_queue/src/contract.rs

ExecuteMsg::ExecuteBid {
 liquidator,
 repay_address,
 fee_address,
 collateral_denom,
 amount,
} => {
 let sender = deps
    .api
    .addr_canonicalize(&info.sender.as_str())?
    .to_string();
 let collateral_token = collateral_denom;
 execute_liquidation(
  deps,
  env,
  sender,
  liquidator,
  repay_address,
  fee_address,
  collateral_token,
  amount,
 )
}

\color{black}\color{white}The QueryMsg::GetPastTotalSupply message is not used:

krp-token-contracts/contracts/ve_seilor/src/contract.rs

QueryMsg::GetPastTotalSupply { block_number } => {
 to_binary(&get_past_total_supply(deps, env, block_number)?)
}
Score
Recommendation

SOLVED: The Kryptonite team solved this issue in commits 80f3137 and dd0a194.

8.51 UNUSED VARIABLES

// Informational

Description

Along the codebase, there are some variables that are unused. This situation is not security-related, but mentioned in the report as part of the best practices in software development to improve the readability of code during all phases of its lifecycle. The affected variables are the following:

  • krp-token-contracts/distribute: RuleConfig.lock_end_time
  • krp-token-contracts/treasure: TreasureState.total_withdraw_amount and TreasureState.total_unlock_amount

Code Location

The lock_end_time variable is not used:

krp-token-contracts/contracts/distribute/src/state.rs

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct RuleConfig {
 pub rule_name: String,
 pub rule_owner: Addr,
 pub rule_total_amount: u128,
 pub start_release_amount: u128,
 pub lock_start_time: u64,
 pub lock_end_time: u64,
 pub start_linear_release_time: u64,
 pub end_linear_release_time: u64,
 pub unlock_linear_release_amount: u128,
 pub unlock_linear_release_time: u64,
 pub linear_release_per_second: u128,
}

\color{black}\color{white}The total_unlock_amount and total_withdraw_amount variables are not used:

krp-token-contracts/contracts/treasure/src/state.rs

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct TreasureState {
 pub current_unlock_amount: Uint128,
 pub current_locked_amount: Uint128,

 pub total_locked_amount: Uint128,
 pub total_unlock_amount: Uint128,
 pub total_withdraw_amount: Uint128,
 pub total_punish_amount: Uint128,
 pub total_cost_dust_amount: Uint128,

 pub total_win_nft_num: u64,
 pub total_lose_nft_num: u64,
}
Score
Recommendation

SOLVED: The Kryptonite team solved this issue in commit 0e7162a.

8.52 USELESS FUNCTIONS

// Informational

Description

The deduct_tax and deduct_tax_new functions in krp-market-contracts/moneymarket/packages do not perform any kind of tax deduction or operation over the input amount. These functions can be removed.

Code Location

Code of the deduct_tax and deduct_tax_new functions in the Moneymarket package:

krp-market-contracts/packages/moneymarket/src/querier.rs

pub fn deduct_tax(_deps: Deps, coin: Coin) -> StdResult<Coin> {
    //let tax_amount = compute_tax(deps, &coin)?;
    Ok(Coin {
        denom: coin.denom,
        //amount: (Uint256::from(coin.amount) - tax_amount).into(),
        amount: Uint256::from(coin.amount).into(),
    })
}

pub fn deduct_tax_new(_deps: Deps, burn_amount: Uint128) -> StdResult<Uint256> {
    // let tax_cap = Uint256::one();
    // let protocal_fee_rate = Decimal256::from_ratio(5, 1000);

    // Ok(std::cmp::min(
    //     Uint256::from(burn_amount) * Decimal256::one() - Uint256::from(burn_amount) / (Decimal256::one() + protocal_fee_rate),
    //     tax_cap,
    //     ))
    Ok(Uint256::from(burn_amount))
}
Score
Recommendation

ACKNOWLEDGED: The Kryptonite team acknowledged this finding.

Halborn strongly recommends conducting a follow-up assessment of the project either within six months or immediately following any material changes to the codebase, whichever comes first. This approach is crucial for maintaining the project’s integrity and addressing potential vulnerabilities introduced by code modifications.

// Download the full report

* Use Google Chrome for best results

** Check "Background Graphics" in the print settings if needed

© Halborn 2024. All rights reserved.