Prepared by:
HALBORN
Last Updated 04/26/2024
Date of Engagement by: August 28th, 2023 - November 1st, 2023
100% of all REPORTED Findings have been addressed
All findings
52
Critical
9
High
7
Medium
6
Low
18
Informational
12
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.
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.
CosmWasm Smart Contracts in scope:
#. 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
#. 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
#. 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
#. 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
#. 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
#. 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
#. 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.
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
)
EXPLOITABILIY METRIC () | METRIC VALUE | NUMERICAL 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 |
IMPACT METRIC () | METRIC VALUE | NUMERICAL 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 |
SEVERITY COEFFICIENT () | COEFFICIENT VALUE | NUMERICAL VALUE |
---|---|---|
Reversibility () | None (R:N) Partial (R:P) Full (R:F) | 1 0.5 0.25 |
Scope () | Changed (S:C) Unchanged (S:U) | 1.25 1 |
Severity | Score Value Range |
---|---|
Critical | 9 - 10 |
High | 7 - 8.9 |
Medium | 4.5 - 6.9 |
Low | 2 - 4.4 |
Informational | 0 - 1.9 |
Critical
9
High
7
Medium
6
Low
18
Informational
12
Security analysis | Risk level | Remediation Date |
---|---|---|
LOANS CAN BE REPAID WITHOUT SPENDING COINS | Critical | Solved - 10/18/2023 |
ARBITRARY MINTING OF COINS WITHOUT DEPOSITING COLLATERALS | Critical | Solved - 10/18/2023 |
NO ACCESS CONTROL IN WITHDRAW FUNCTION | Critical | Solved - 10/21/2023 |
NO ACCESS CONTROL IN REPAYMENT ENTRY POINT | Critical | Solved - 10/21/2023 |
ARBITRARY MINTING OF COINS USING FAKE CW20 TOKENS | Critical | Solved - 10/18/2023 |
COLLATERAL BALANCE OF LIQUIDATED USERS DOES NOT DECREASE | Critical | Solved - 10/19/2023 |
LIQUIDATED LOANS WITHOUT AN ADEQUATE REPAYMENT | Critical | Solved - 10/18/2023 |
ARBITRARY REPAYMENT OF COINS FROM LIQUIDATIONS | Critical | Solved - 10/18/2023 |
MISCALCULATION OF MAX LOAN TO VALUE WHEN MINTING COINS | Critical | Solved - 10/18/2023 |
COLLATERAL LIQUIDATION IS NOT WORKING AS EXPECTED | High | Solved - 10/18/2023 |
BID FEE IS NOT SENT | High | Solved - 10/19/2023 |
LIQUIDATOR FEE IS NOT SENT | High | Solved - 10/19/2023 |
MISCALCULATION OF MAX LOAN TO VALUE WHEN QUERYING AVAILABLE COLLATERAL | High | Solved - 10/18/2023 |
REWARDS BONDING FUNCTIONALITY IS UNAVAILABLE | High | Solved - 10/19/2023 |
WITHOUT POSSIBILITY TO SWAP ALL NATIVE TOKENS TO REWARD DENOMINATION | High | Solved - 10/19/2023 |
UNCHECKED BALANCE CHANGE COULD LEAD TO UNFAIR WITHDRAWALS | High | Solved - 10/19/2023 |
INADEQUATE TRACKING OF PENDING REDELEGATIONS | Medium | Solved - 10/19/2023 |
MAX NUMBER OF BID SLOTS COULD BE MODIFIED AFTER BIDS WERE SUBMITTED | Medium | Risk Accepted - 11/01/2023 |
SOME FUNCTIONS RECEIVE MULTIPLE NATIVE COINS INSTEAD OF ONE | Medium | Solved - 10/24/2023 |
REDELEGATION IS NOT RESTRICTED TO ACTIVE VALIDATORS | Medium | Solved - 10/20/2023 |
COIN DENOMINATION IS NOT CHECKED WHEN REMOVING VALIDATORS | Medium | Risk Accepted - 10/24/2023 |
INADEQUATE TRACKING OF SWAPPED AMOUNTS | Medium | Solved - 10/23/2023 |
UNBOND WAIT LISTS CAN BE MIGRATED EVEN IF THE CONTRACT IS NOT PAUSED | Low | Solved - 10/20/2023 |
UNCHECKED MAX LOAN-TO-VALUE RATIO | Low | Solved - 10/19/2023 |
FUNCTIONALITY TO UPDATE FEES IS NOT APPLIED CORRECTLY | Low | Solved - 10/20/2023 |
ARBITRARY MESSAGES CAN BE EXECUTED ON BEHALF OF THE HUB | Low | Solved - 10/20/2023 |
UNRELIABLE SOURCE OF RANDOMNESS | Low | Solved - 10/30/2023 |
WITHDRAWAL COULD GET STUCK IF THERE ARE NO MORE UNBONDINGS | Low | Risk Accepted - 10/24/2023 |
OWNERSHIP CAN BE TRANSFERRED WITHOUT CONFIRMATION | Low | Solved - 10/26/2023 |
COMMENTED TRANSACTION MESSAGE | Low | Solved - 11/21/2023 |
UNCHECKED REDEEM FEE | Low | Solved - 10/18/2023 |
UNCHECKED KEEPER RATE | Low | Solved - 10/20/2023 |
MAXIMUM AMOUNT OF TOKENS TO MINT IS NOT VALIDATED | Low | Solved - 10/25/2023 |
UNCHECKED PARAMETERS IN DISPATCHER CONTRACT | Low | Solved - 10/25/2023 |
UNCHECKED PARAMETERS IN TREASURE CONTRACT | Low | Solved - 10/30/2023 |
DURATION IS NOT VALIDATED IN STAKING CONTRACT | Low | Solved - 10/30/2023 |
CLAIMABLE TIME IS NOT VALIDATED IN FUND CONTRACT | Low | Solved - 10/31/2023 |
EXCHANGE RATE COULD INCREASE INDEFINITELY | Low | Solved - 10/20/2023 |
PAIR KEY CAN CONTAIN DUPLICATED ASSETS | Low | Solved - 10/23/2023 |
ASSETS COULD MISMATCH WITH THE ONES IN PAIR ADDRESS | Low | Solved - 10/24/2023 |
IMMUTABLE VARIABLES CAN BE CHANGED IN STAKING CONTRACT | Informational | Solved - 10/30/2023 |
MARKETING INFO IS NOT VALIDATED AT INSTANTIATION | Informational | Solved - 10/25/2023 |
LOCK AMOUNT IN GLOBAL STATE IS NOT VALIDATED WHEN CLAIMING | Informational | Solved - 10/25/2023 |
UNCHECKED VALIDATOR ADDRESSES | Informational | Solved - 10/20/2023 |
UNCHECKED SAFE RATIO | Informational | Solved - 10/19/2023 |
REDUNDANT LOGIC | Informational | Solved - 10/21/2023 |
REPEATED EXECUTION MESSAGES | Informational | Acknowledged - 10/24/2023 |
STAKING TOKEN CALLS CHECK SLASHING TWICE | Informational | Solved - 10/24/2023 |
UNIMPLEMENTED MESSAGE | Informational | Solved - 10/20/2023 |
UNUSED MESSAGES | Informational | Solved - 10/25/2023 |
UNUSED VARIABLES | Informational | Solved - 10/30/2023 |
USELESS FUNCTIONS | Informational | Acknowledged - 10/21/2023 |
// Critical
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.
There is no access control in the repay_stable_coin
function:
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()),
]))
}
SOLVED: The Kryptonite team
solved this issue in commit 64d3a2d.
// Critical
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.
There is no access control in the mint_stable_coin
function:
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![];
SOLVED: The Kryptonite team
solved this issue in commit 4c5310b.
// Critical
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 fragment of the withdraw_collateral
function of the krp-market-contracts/custody_bsei contract:
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(),
})?,
}))
SOLVED: The Kryptonite team
solved this issue in commit 717dbe0.
// Critical
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.
There is no access control in the repay_stable_from_yield_reserve
function:
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))
}
SOLVED: The Kryptonite team
solved this issue in commit e43051d.
// Critical
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.
There is no access control in the mint_stable_coin
function:
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);
SOLVED: The Kryptonite team
solved this issue in commit 1e6a609.
// Critical
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.
The DecreaseBalance
message is not called in the liquidate_collateral
function:
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(),
})?,
}))
}
}
SOLVED:The Kryptonite team
solved this issue in commit 4a00da9.
// Critical
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:
liquidate_collateral
function in the custody contract.There is no access control in the liquidate_collateral
function:
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)?;
SOLVED: The Kryptonite team
solved this issue in commit 2997c00.
// Critical
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:
repay_stable_from_liquidation
function from the stable_pool contract using pre_balance
= 0 and minter
= There is no access control in the repay_stable_from_liquidation
function:
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)
}
SOLVED: The Kryptonite team
solved this issue in commit 41ae7eb.
// Critical
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}
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;
}
SOLVED: The Kryptonite team
solved this issue in commit 9e26c73.
// High
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:
let pre_balance: Uint256 = query_balance(
deps.as_ref(),
env.contract.address.clone(),
config.stable_denom.to_string(),
)?;
SOLVED: The Kryptonite team
solved this issue in commit 2b1e9bb.
// High
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 fragment of the execute_liquidation
function in the krp-market-contracts/liquidation_queue contract:
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(),
},
)?],
}));
}
// High
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 fragment of the execute_liquidation
function of the krp-market-contracts/liquidation_queue contract:
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(),
},
)?],
}));
}
// High
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}
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;
}
}
SOLVED: The Kryptonite team
solved this issue in commit 0950837.
// High
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.
The krp-staking-contracts/basset_sei_rewards_dispatcher contract does not call the ExecuteMsg::BondRewards
message:
#[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)
}
}
}
SOLVED: The Kryptonite team
solved this issue in commit a682bc4.
// High
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.
The krp-staking-contracts/basset_sei_rewards_dispatcher contract does not call the ExecuteMsg::SwapToRewardDenom
message:
#[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)
}
}
}
SOLVED: The Kryptonite team
solved this issue in commit a5c858c.
// High
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):
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,
);
SOLVED: The Kryptonite team
solved this issue in commit 9c0d95a.
// Medium
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.
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())?;
SOLVED: The Kryptonite team
solved this issue in commit ab8ef52.
// Medium
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 fragment of the update_collateral_info
function in the krp-market-contracts/liquidation_queue contract:
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"))
}
RISK ACCEPTED: The Kryptonite team
accepted the risk of this finding.
// Medium
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:
execute_convert_to_basset
functionswap_denom
functionCode fragment of the execute_convert_to_basset
function in the krp-basset-convert/krp_basset_converter contract:
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;
// Medium
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.
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();
SOLVED: The Kryptonite team
solved this issue in commit 7de9f39.
// Medium
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.
There is no check about coin denomination in the remove_validator
function:
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()),
));
}
RISK ACCEPTED: The Kryptonite team
accepted the risk of this finding.
// Medium
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.
// 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;
SOLVED: The Kryptonite team
solved this issue in commit 22e954a.
// Low
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.
The migrate_unbond_wait_lists
function assumes that the contract is pause without verifying it:
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, ¶ms)?;
}
Ok(Response::new().add_attributes(vec![
attr("action", "migrate_unbond_wait_lists"),
attr("num_migrated_entries", num_migrated_entries.to_string()),
]))
}
SOLVED: The Kryptonite team
solved this issue in commit 37cb49a.
// Low
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.
The whitelist_collateral
function in the krp-cdp-contracts/central_control contract:
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())
}
// Low
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.3liquidator_fee
= 0.6New values:
bid_fee
= 0.5liquidator_fee
= 0.4The 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.
Snippet of update_config
function in the krp-market-contracts/liquidation_queue contract:
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;
}
// Low
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.
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![],
}))
}
}
SOLVED: The Kryptonite team
solved this issue in commit 31b6d03.
// Low
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:
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.
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;
}
}
SOLVED: The Kryptonite team
solved this issue in commit 9d12155.
// Low
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.
Relevant code fragments from the execute_unbond
and execute_unbond_stsei
functions:
// 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);
}
// 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);
}
RISK ACCEPTED: The Kryptonite team
accepted the risk of this finding.
// Low
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:
Example - Code of the update_config
function in the krp-market-contracts/custody_base contract:
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")]))
}
// Low
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 fragment of the claim_rewards
function in the krp-market-contracts/market contract:
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),
]))
SOLVED: The Kryptonite team
solved this issue in commits e766b7c.
// Low
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.
The instantiate
and update_config
functions do not verify that redeem_fee
is lower than 1:
#[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())
}
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())
SOLVED: The Kryptonite team
solved this issue in commit 54b01dd.
// Low
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.
The instantiate
and execute_update_config
functions do not verify that krp_keeper_rate
is lower than 1:
#[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())
}
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)
})?;
}
SOLVED: The Kryptonite team
solved this issue in commit c20f645.
// Low
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.
The update_config
function does not verify that max_minted
should be greater than total_minted
:
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()));
}
SOLVED: The Kryptonite team
solved this issue in commit 6b82713.
// Low
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 timeduration_per_period
> 0periods
> 0If one of those parameters is mistakenly set, it could arise in unexpected situations, e.g.: panicking when users try to claim.
The instantiate
or update_config
functions do not verify the appropriate boundaries:
#[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"))
}
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))
// Low
The instantiate
or update_config
functions in the krp-token-contracts/treasure contract do not verify the following parameters:
start_lock_time
>= current block timeend_lock_time
> start_lock_time
nft_start_pre_mint_time
>= current block timenft_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.
The instantiate
or update_config
functions do not verify the appropriate boundaries:
#[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,
};
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()));
}
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(),
));
}
SOLVED: The Kryptonite team
solved this issue in commit 8776456.
// Low
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.
The instantiate
and update_staking_duration
functions do not verify that duration
is greater than 0:
#[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(),
};
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
SOLVED: The Kryptonite team
solved this issue in commit 9def1db.
// Low
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.
The instantiate
and update_fund_config
functions do not verify that claim_able_time
is greater than the current block time:
#[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,
};
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))
SOLVED: The Kryptonite team
solved this issue in commit d17a174.
// Low
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.
The execute_burn
function in the basset_sei_token_bsei and basset_sei_token_stsei contracts is not adequately restricted:
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)?;
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)?;
SOLVED: The Kryptonite team
solved this issue in commit 67cc445.
// Low
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.
The update_pair_config
function does not verify that asset_infos
contains different assets:
#[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()), ]))
}
SOLVED: The Kryptonite team
solved this issue in commit 3ea0429.
// Low
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.
The update_pair_config
function does not verify that the assets in pair_address
are the same ones as in asset_infos
:
#[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()), ]))
}
SOLVED: The Kryptonite team
solved this issue in commit aa6fd58.
// Informational
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.
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()));
}
SOLVED: The Kryptonite team
solved this issue in commit c8afb00.
// Informational
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:
The marketing
parameter is optional when instantiating the krp-staking-contracts/basset_sei_token_stsei contract:
#[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:
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:
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
};
// Informational
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.
The user_claim
function only validates that claimed_lock_amount
<= total_user_lock_amount
for the user state, but not for the global state:
// 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)?;
SOLVED: The Kryptonite team
solved this issue in commit 6ce1c7e.
// Informational
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.
The instantiate
function does not use addr_validate
before saving the validator addresses to storage:
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:
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())
}
SOLVED: The Kryptonite team
solved this issue in commit f3c1794.
// Informational
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.
Snippet of update_config
function in the krp-market-contracts/liquidation_queue contract:
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;
}
// Informational
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.
Redundant logic in the execute_convert_to_basset
function:
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:
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",
));
}
SOLVED: The Kryptonite team
solved this issue in commit 2bd0553.
// Informational
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.
The ExecuteMsg::MigrateUnbondWaitList
and ExecuteMsg::UpdateParams
messages are repeated in the execute
function:
#[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,
);
}
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,
),
ExecuteMsg::RedelegateProxy {
src_validator,
redelegations,
} => execute_redelegate_proxy(deps, env, info, src_validator, redelegations),
ExecuteMsg::MigrateUnbondWaitList { limit: _ } => Err(StdError::generic_err("forbidden")),
ACKNOWLEDGED: The Kryptonite team
acknowledged this finding.
// Informational
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.
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![],
})))
}
SOLVED: The Kryptonite team
solved this issue in commit e7c4805.
// Informational
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.
#[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!(),
}
}
SOLVED: The Kryptonite team
solved this issue in commit 3debcf9.
// Informational
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:
ExecuteMsg::ExecuteBid
QueryMsg::GetPastTotalSupply
The ExecuteMsg::ExecuteBid
message is not used:
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:
QueryMsg::GetPastTotalSupply { block_number } => {
to_binary(&get_past_total_supply(deps, env, block_number)?)
}
// Informational
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:
RuleConfig.lock_end_time
TreasureState.total_withdraw_amount
and TreasureState.total_unlock_amount
The lock_end_time
variable is not used:
#[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:
#[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,
}
SOLVED: The Kryptonite team
solved this issue in commit 0e7162a.
// Informational
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 of the deduct_tax
and deduct_tax_new
functions in the Moneymarket package:
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))
}
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