Prepared by:
HALBORN
Last Updated 04/26/2024
Date of Engagement by: January 17th, 2022 - January 28th, 2022
100% of all REPORTED Findings have been addressed
All findings
2
Critical
0
High
0
Medium
1
Low
1
Informational
0
Mars Protocol
engaged Halborn to conduct a security assessment on CosmWasm smart contracts beginning on January 17th, 2022 and ending January 28th, 2022.
The security engineers involved on the audit 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 audit is to achieve the following:
Ensure that smart contract functions work 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 Mars team
. The main ones are the following:
Enforce the use of a valid routine in update_config
Enforce check of arithmetic operations
External threats, such as financial related attacks, oracle attacks, and inter-contract functions and calls should be validated for expected logic and state.
Halborn performed a combination of manual review of the code and automated security testing to balance efficiency, timeliness, practicality, and accuracy regarding the scope of the smart contract audit. 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 audit:
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
) \newline
\begin{enumerate} \item CosmWasm Smart Contracts \begin{enumerate} \item Repository: \href{https://github.com/mars-protocol/fields-of-mars}{fields-of-mars} \item Commit ID: \href{https://github.com/mars-protocol/fields-of-mars/tree/dc2245ada036d7cfef94a82dd121474d1dd033f2 }{dc2245ada036d7cfef94a82dd121474d1dd033f2} \item Contracts in scope: \begin{enumerate} \item martian-field \end{enumerate} \end{enumerate} \end{enumerate}
Out-of-scope:
External libraries and financial related attacks
Critical
0
High
0
Medium
1
Low
1
Informational
0
Impact x Likelihood
HAL-01
HAL-02
Security analysis | Risk level | Remediation Date |
---|---|---|
CONFIG PARAMETERS VALUE CAN BE CHANGED UNRESTRICTEDLY | Medium | Solved - 02/09/2022 |
SOME RATES COULD BE SET TO VALUES GREATER THAN 1 | Low | Solved - 02/09/2022 |
// Medium
instantiate
and update_config
functions in contracts/martian-field/src/execute.rs allow contract's owner to update max_ltv
, bonus_rate
and fee_rate
fields with a potential unfair amount. This situation can produce the following consequences:
max_ltv
to very low rate e.g., : 0.01 and bonus_rate
to e.g., : 0 and liquidate all positions, draining users assets.max_ltv
rate to lower than released one, which could become current positions into “unhealthy” ones and ready to be liquidated. fee_rate
is equal to 1, harvest operations will cause unfair reward distributions and owner could drain user assets. Furthermore, if fee_rate
is higher than 1 it will cause an overflow.It is worth noting that likelihood for this to happen is low because martian-field contract is intended to be owned by governance (Council) indefinitely, who is the responsible one for this operation.
pub fn update_config(deps: DepsMut, info: MessageInfo, new_config: Config) -> StdResult<Response> {
let config = CONFIG.load(deps.storage)?;
if info.sender != config.governance {
return Err(StdError::generic_err("only governance can update config"));
}
CONFIG.save(deps.storage, &new_config)?;
Ok(Response::default())
}
pub fn liquidate(
deps: DepsMut,
env: Env,
info: MessageInfo,
user_addr: Addr,
) -> StdResult<Response> {
let config = CONFIG.load(deps.storage)?;
let state = STATE.load(deps.storage)?;
let position = POSITION.load(deps.storage, &user_addr).unwrap_or_default();
// position must be active (LTV is not `None`) and the LTV must be greater than `max_ltv`
let health = compute_health(&deps.querier, &env, &config, &state, &position)?;
// if `health.ltv` is `Some`, it must be greater than `max_ltv`
// if `health.ltv` is `None`, indicating the position is already closed, then it is not liquidatable
let ltv = health.ltv.ok_or_else(|| StdError::generic_err("position is already closed"))?;
if ltv <= config.max_ltv {
return Err(StdError::generic_err("position is healthy"));
}
// 1. unbond the user's liquidity tokens from Astro generator
// 2. burn liquidity tokens, withdraw primary + secondary assets from the pool
// 3. swap all primary assets to secondary assets
// 4. repay all debts
// 5. among all remaining assets, send the amount corresponding to `bonus_rate` to the liquidator
// 6. refund all assets that're left to the user
//
// NOTE: in the previous versions, we sell **all** primary assets, which is not optimal because
// this will incur bigger slippage, causing worse liquidation cascade, and be potentially lucrative
// for sandwich attackers
//
// now, we calculate how much additional secondary asset is needed to fully pay off debt, and
// reverse-simulate how much primary asset needs to be sold
//
// TODO: add slippage checks to the swap step so that liquidation cannot be sandwich attacked
let callbacks = [
CallbackMsg::Unbond {
user_addr: user_addr.clone(),
bond_units_to_reduce: position.bond_units,
},
CallbackMsg::WithdrawLiquidity {
user_addr: user_addr.clone(),
},
CallbackMsg::Cover {
user_addr: user_addr.clone(),
},
CallbackMsg::Repay {
user_addr: user_addr.clone(),
repay_amount: health.debt_value,
},
CallbackMsg::Refund {
user_addr: user_addr.clone(),
recipient_addr: info.sender.clone(),
percentage: config.bonus_rate,
},
CallbackMsg::Refund {
user_addr: user_addr.clone(),
recipient_addr: user_addr.clone(),
percentage: Decimal::one(),
},
];
SOLVED: The issue was fixed in commit 0f9c959931fcde3ddf5cdb1907c9177f69284e31.
// Low
The instantiate
and update_config
functions in contracts/martian-field/src/execute.rs do not restrict that rates fields
are lesser than 1.
If they are not correctly set, some operations will always panic and won't allow legitimate users to harvest
or liquidate
; thus generating a denial of service (DoS). The affected fields are the following:
pub fn update_config(deps: DepsMut, info: MessageInfo, new_config: Config) -> StdResult<Response> {
let config = CONFIG.load(deps.storage)?;
if info.sender != config.governance {
return Err(StdError::generic_err("only governance can update config"));
}
CONFIG.save(deps.storage, &new_config)?;
Ok(Response::default())
}
SOLVED: The issue was fixed in the following commits:
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