Halborn Logo

Stader Labs LunaX Contracts - Stader Labs


Prepared by:

Halborn Logo

HALBORN

Last Updated 04/26/2024

Date of Engagement by: January 17th, 2022 - February 7th, 2022

Summary

100% of all REPORTED Findings have been addressed

All findings

7

Critical

0

High

0

Medium

1

Low

1

Informational

5


1. INTRODUCTION

Stader Labs engaged Halborn to conduct a security audit on their smart contracts beginning on January 17th, 2022 and ending on February 7th, 2022. The security assessment was scoped to the LunaX related smart contracts provided to the Halborn team.

2. AUDIT SUMMARY

The team at Halborn was provided three weeks for the engagement and assigned two security engineers to audit the security of the smart contract. 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 audit 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 multiple risks, which has been addressed by Stader Labs team. The main ones are the following:

    • Add manager transfer capabilities to allow changes on the privileged account of the contracts.

    • Check the "Protocol Inactive" state on all functionalities related to fund updates, stake tracking or swap contract interaction.

3. 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 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

4. SCOPE

Code repository: https://github.com/stader-labs/stader-liquid-token

\begin{enumerate} \item CosmWasm Smart Contracts - LunaX \begin{enumerate} \item Commit ID: \href{https://github.com/stader-labs/stader-liquid-token/tree/e2561633859cc846bea024854b61248dd6f28665}{e2561633859cc846bea024854b61248dd6f28665} \item Contracts in scope: \begin{enumerate} \item airdrops-registry \item reward \item staking \end{enumerate} \end{enumerate} \end{enumerate}

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

5. RISK METHODOLOGY

Vulnerabilities or issues observed by Halborn are ranked based on the risk assessment methodology by measuring the LIKELIHOOD of a security incident and the IMPACT should an incident occur. This framework works for communicating the characteristics and impacts of technology vulnerabilities. The quantitative model ensures repeatable and accurate measurement while enabling users to see the underlying vulnerability characteristics that were used to generate the Risk scores. For every vulnerability, a risk level will be calculated on a scale of 5 to 1 with 5 being the highest likelihood or impact.
RISK SCALE - LIKELIHOOD
  • 5 - Almost certain an incident will occur.
  • 4 - High probability of an incident occurring.
  • 3 - Potential of a security incident in the long term.
  • 2 - Low probability of an incident occurring.
  • 1 - Very unlikely issue will cause an incident.
RISK SCALE - IMPACT
  • 5 - May cause devastating and unrecoverable impact or loss.
  • 4 - May cause a significant level of impact or loss.
  • 3 - May cause a partial impact or loss to many.
  • 2 - May cause temporary impact or loss.
  • 1 - May cause minimal or un-noticeable impact.
The risk level is then calculated using a sum of these two values, creating a value of 10 to 1 with 10 being the highest level of security risk.
Critical
High
Medium
Low
Informational
  • 10 - CRITICAL
  • 9 - 8 - HIGH
  • 7 - 6 - MEDIUM
  • 5 - 4 - LOW
  • 3 - 1 - VERY LOW AND INFORMATIONAL

6. SCOPE

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

7. Assessment Summary & Findings Overview

Critical

0

High

0

Medium

1

Low

1

Informational

5

Impact x Likelihood

HAL-01

HAL-02

HAL-03

HAL-05

HAL-06

HAL-07

HAL-04

Security analysisRisk levelRemediation Date
MANAGER ADDRESS CANNOT BE TRANSFERREDMediumSolved - 02/08/2022
UNDERUSED PROTOCOL INACTIVE STATELowSolved - 02/08/2022
CONFIGURATION PARAMETER COULD NOT BE UPDATEDInformationalAcknowledged
OUTDATED INFORMATION DISPLAYED TO USERSInformationalSolved - 02/08/2022
MISUSE OF HELPER METHODSInformationalSolved - 02/08/2022
MULTIPLE INSTANCES OF UNCHECKED MATHInformationalAcknowledged
UNFINISHED DEVELOPMENT COMMENTSInformationalSolved - 02/08/2022

8. Findings & Tech Details

8.1 MANAGER ADDRESS CANNOT BE TRANSFERRED

// Medium

Description

The functions to update the configuration in the contracts within scope lacked the option of setting a new manager as a privileged address. If the keys of the manager account were suspected to be compromised, or the development team needed to change the address for an operational reason, a sizable portion of the contract's functionality will be rendered unusable.

Code Location

Affected functions

contracts/airdrops-registry/src/contract.rs:59:pub fn update_airdrop_registry(
contracts/reward/src/contract.rs:204:pub fn update_config(
contracts/staking/src/contract.rs:143:pub fn update_config(
Score
Impact: 4
Likelihood: 2
Recommendation

SOLVED: The issue was fixed with the above recommendation in commit 849a43b9c96cb6b70b67a0bb60aaa5c0be221d1e.

8.2 UNDERUSED PROTOCOL INACTIVE STATE

// Low

Description

The staking contract implemented a switch to go into a pause-like state called “Protocol Inactive” by using the config.active setting. This check was not implemented consistently across all the functions accessible for unprivileged users, but only on the deposit one.

This state could be desirable for some reasons, where external users' interference would like to be kept to the minimum among all features of the contract.

Code Location

Affected assets

contracts/staking/src/contract.rs:486 redeem_rewards function
contracts/staking/src/contract.rs:523 swap_rewards function
contracts/staking/src/contract.rs:486 reinvest function
contracts/staking/src/contract.rs:615 reimburse_slashing function
contracts/staking/src/contract.rs:715 undelegate_stake function
contracts/staking/src/contract.rs:810 reconcile_funds function
contracts/staking/src/contract.rs:888 withdraw_funds_to_wallet function
contracts/staking/src/contract.rs:976 claim_airdrops function
Score
Impact: 3
Likelihood: 1
Recommendation

SOLVED: A new "operations control" mechanism has been implemented to have a more granular control over paused operations, covering all listed functionalities.

This issue was fixed in commit 368f9a665af2fd6d512d2687e40f8699b7a6e164.

8.3 CONFIGURATION PARAMETER COULD NOT BE UPDATED

// Informational

Description

The instantiate function did not set the cw20_token_contract address, as done with other contract addresses required in the configuration. Instead, it relied on update_config being called post initialization, which could cause undesirable situations if this address is not set right after deployment.

It is worth noting that the update_config function only allowed to set the CW20 address if it contained the initial value Addr::unchecked("0"). This effectively forbade any future change after the first update.

Code Location

contracts/staking/src/contract.rs

airdrop_registry_contract: deps
    .api
    .addr_validate(msg.airdrops_registry_contract.as_str())?,
airdrop_withdrawal_contract: deps
    .api
    .addr_validate(msg.airdrop_withdrawal_contract.as_str())?,
reward_contract: deps.api.addr_validate(msg.reward_contract.as_str())?,
cw20_token_contract: Addr::unchecked("0"),

protocol_fee_contract: deps.api.addr_validate(msg.protocol_fee_contract.as_str())?,

contracts/staking/src/contract.rs

pub fn update_config(
    deps: DepsMut,
    info: MessageInfo,
    env: Env,
    update_config: ConfigUpdateRequest,
) -> Result<Response, ContractError> {
    let mut config = CONFIG.load(deps.storage)?;
    validate(&config, &info, &env, vec![Verify::SenderManager])?;

    if let Some(cw20_contract) = update_config.cw20_token_contract {
        if config.cw20_token_contract == Addr::unchecked("0") {
            config.cw20_token_contract = deps.api.addr_validate(cw20_contract.as_str())?;
        }
    }
Score
Impact: 1
Likelihood: 1
Recommendation

ACKNOWLEDGED: Stader Labs acknowledged this finding.

8.4 OUTDATED INFORMATION DISPLAYED TO USERS

// Informational

Description

The reimburse_slashing function of the staking contract does not update the total_staked and exchange_rate state variables. As detailed in the comment of line 635, most functionalities perform a check_slashing at the beginning or update these values. However, this is not the case of query_user_info and query_compute_deposit_breakdown.

If a user queries those functions after a reimburse_slashing has been performed, they would receive erroneous information due to the lack of update.

Code Location

Affected assets

contracts/staking/src/contract.rs:615 reimburse_slashing function
Score
Impact: 1
Likelihood: 2
Recommendation

SOLVED: Added a RedeemRewards message from the staking contract as a result of the reimburse_slashing function, which effectively updates the exchange rate at the end of the transaction.

This issue was fixed on commit 24ec25b51e52e98c1128cd77c379d94b3e9dab58s.

8.5 MISUSE OF HELPER METHODS

// Informational

Description

The use of the unwrap and expect function is very useful for testing environments because a value is forcibly demanded to get an error (aka panic!) if the "Option" does not have "Some" value or "Result". Nevertheless, leaving unwrap or expect functions in production environments is a bad practice because not only will this cause the program to crash out, or panic!, but also (in case of unwrap) no helpful messages are shown to help the user solve, or understand the reason of the error.

Code Location

Affected assets

contracts/reward/src/contract.rs: #L93, 168, 171, 217
contracts/staking/src/contract.rs: #L266, 361, 371, 400, 433, 579, 632, 696, 701, 743, 773, 785, 830, 836, 867, 880, 946, 956, 1020, 1108, 1121, 1137
contracts/staking/src/helpers.rs: #L90, 98, 122, 163
Score
Impact: 1
Likelihood: 1
Recommendation

SOLVED: Most of the instances highlighted below has been fixed using the recommendations or deemed secure due to previous checks. Instances related to the result of checked_add operations have not been modified, as the risk of overflow in those cases is minimal, since the entire supply of Terra supply will not cause the value of uint128 to overflow.

This issue was fixed in commit 7478e89ee9a0f72b89690573fd2dee956d2408fa.

8.6 MULTIPLE INSTANCES OF UNCHECKED MATH

// Informational

Description

Some mathematical operations that could cause unexpected behavior under specific circumstances were found on the codebase. Although no effective arithmetic over/underflow were found and the overflow-checks = true flag was set on Cargo.toml, it is still recommended to avoid unchecked math as much as possible to follow best-practices and limit the risk of future updates introducing an actual vulnerability.

Code Location

Affected assets

packages/stader-utils/src/coin_utils.rs:173:    let c_u256: Decimal = (b_u256 * a_u256).into();
packages/stader-utils/src/coin_utils.rs:181:    let c_u256: Decimal = (b_u256 + a_u256).into();
packages/stader-utils/src/coin_utils.rs:189:    let c_u256: Decimal = (a_u256 - b_u256).into();
packages/stader-utils/src/coin_utils.rs:243:                Uint128::new(coin.amount.u128() + existing_coin.u128()),
packages/stader-utils/src/coin_utils.rs:278:                Uint128::new(existing_coin.u128() - coin.amount.u128()),
packages/stader-utils/src/coin_utils.rs:432:        coin.amount.u128() * ratio.numerator() / ratio.denominator(),
packages/stader-utils/src/coin_utils.rs:437:    (num * dec.numerator() / dec.denominator()) as u128
Score
Impact: 1
Likelihood: 1
Recommendation

ACKNOWLEDGED: Since the affected instances were not exploitable and therefore did not pose a direct risk to in-scope contracts, Stader Labs acknowledged the potential risks outlined above.

8.7 UNFINISHED DEVELOPMENT COMMENTS

// Informational

Description

Multiple “ToDo” comments and commented code instances were found on the codebase. Although incomplete code does not directly cause a security vulnerability or affect the audit’s outcome as far as it is functional, having development comments could simplify the process for an attacker to find a valid attack surface within the contract.

In addition, it shows that the audited code will be different from the released one, which could cause that new vulnerabilities were to be introduced in the code after the audit.

Code Location

Affected assets

staking/src/helpers.rs:29:// TODO: bchain99 - write unit-tests to validate.
staking/src/contract.rs:1092:// TODO - GM. Test this
staking/src/contract.rs:1101:    // TODO - GM. Will converting u64 to string for batch id start work?
staking/src/contract.rs:521:// TODO - GM. Does swap have a fixed cost or a linear cost?
reward/src/contract.rs:77:// TODO - GM. Does swap have a fixed cost or a linear cost? Useful to make this permissionless.
reward/src/contract.rs:94:    // let denoms: Vec<String> = total_rewards
reward/src/contract.rs:95:    //     .iter()
reward/src/contract.rs:96:    //     .map(|item| item.denom.clone())
reward/src/contract.rs:97:    //     .collect();

Score
Impact: 1
Likelihood: 1
Recommendation

SOLVED: Suggested mitigation steps have been implemented by the Stader Labs team in commit 07878e2eefa1e66a9a25ad3dc234340a355b5ad0.

9. Automated Testing

AUTOMATED ANALYSIS

Description

Halborn used automated security scanners to assist with detection of well-known security issues and vulnerabilities. Among the tools used was cargo audit, a security scanner for vulnerabilities reported to the RustSec Advisory Database. All vulnerabilities published in https://crates.io are stored in a repository named The RustSec Advisory Database. cargo audit is a human-readable version of the advisory database which performs a scanning on Cargo.lock. Security Detections are only in scope. To better assist the developers maintaining this code, the auditors are including the output with the dependencies tree, and this is included in the cargo audit output to better know the dependencies affected by unmaintained and vulnerable crates.

\begin{center} \begin{tabular}{|l|p{2cm}|p{9cm}|} \hline \textbf{ID} & \textbf{package} & \textbf{Short Description} \ \hline \href{https://rustsec.org/advisories/RUSTSEC-2020-0025}{RUSTSEC-2020-0025} & bigint & biginit is unmaintained, use uint instead \ \hline \end{tabular} \end{center}

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.

© Halborn 2024. All rights reserved.