Prepared by:
HALBORN
Last Updated 10/14/2024
Date of Engagement by: September 25th, 2024 - October 8th, 2024
100% of all REPORTED Findings have been addressed
All findings
3
Critical
0
High
0
Medium
0
Low
0
Informational
3
Solayer
team engaged Halborn
to conduct a security assessment on their USDC Pool
Solana program beginning on September 25th, 2024, and ending on October, 8th, 2024. The security assessment was scoped to the Solana Program provided in solayer-labs/usdc-pool-program GitHub repository. Commit hashes and further details can be found in the Scope section of this report.
The USDC Pool
program has both administrative and user-facing instructions, and is a system designed to be fully async from users' perspective. Users are provided with a proof of deposit or withdraw (PDA), and the platform handles the withdraw and deposit requests in batch. As Solayer
is currently leveraging the OpenEden
yield generation protocol, these batch transactions are essentially USDC
and TBill
token transfers between Solayer
and OpenEden
ATAs, in order to be able to mint/burn the respective amount of sUSD (Solayer USD)
.
The batch transactions are scheduled to occur once a day, and are handled by the Off-Chain Operator
, which is the signer for crucial operations of the USDC Pool
program.
Halborn
was provided 2 weeks for the engagement and assigned one full-time security engineer to review the security of the Solana Program in scope. The engineer is a blockchain and smart contract security expert with advanced smart contract hacking skills, and deep knowledge of multiple blockchain protocols.
The purpose of the assessment is to:
Identify potential security issues within the USDC Pool
Solana Program.
Ensure that the program's functionality operates as intended.
In summary, Halborn
identified some non-critical issues, that were acknowledged by the Solayer team
:
Centralization Risk.
Use of msg!
consumes additional computational budget.
Missing two-step authority transfer mechanism.
Overall, the USDC Pool
program in-scope is adherent to Solana's best-practices and carries consistent code quality.
Halborn
performed a combination of a manual review of the source code and automated security testing to balance efficiency, timeliness, practicality, and accuracy in regard to the scope of the program assessment. While manual testing is recommended to uncover flaws in business logic, processes, and implementation; automated testing techniques help enhance coverage of programs 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 the architecture, purpose, and use of the platform.
Manual program source code review to identify business logic issues.
Mapping out possible attack vectors.
Thorough assessment of safety and usage of critical Rust variables and functions in scope that could lead to arithmetic vulnerabilities.
Scanning dependencies for known vulnerabilities (cargo audit
).
Local runtime testing (solana-test-framework
).
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
0
High
0
Medium
0
Low
0
Informational
3
Security analysis | Risk level | Remediation Date |
---|---|---|
Centralization risk | Informational | Acknowledged - 10/10/2024 |
Use of 'msg!' consumes additional computational budget | Informational | Acknowledged - 10/10/2024 |
Missing multi-step authority transfer mechanism | Informational | Acknowledged - 10/10/2024 |
// Informational
Currently, the operator
account is the only signer of critical batch deposit and withdraw operations, handling significant amount of tokens multiple tokens, such as USDC
, TBill
and sUSD (Solayer USD)
.
It is not mentioned in the documentation that it is a requirement for the operator
account to be a multi-signature, such as Squads, neither the instructions require more than one signer, in order to leverage granularity.
In a scenario where the operator
account gets compromised or the access to this account is lost, the entire functionality of the platform is compromised.
- programs/usdc-pool-program/src/contexts/initiate_batch_deposit.rs
#[derive(Accounts)]
pub struct InitiateBatchDeposit<'info> {
#[account(mut)]
operator: Signer<'info>,
- programs/usdc-pool-program/src/contexts/initiate_batch_withdraw.rs
#[derive(Accounts)]
pub struct InitiateBatchWithdraw<'info> {
#[account(mut)]
operator: Signer<'info>,
- programs/usdc-pool-program/src/contexts/resolve_batch_deposit.rs
#[derive(Accounts)]
pub struct ResolveBatchDeposit<'info> {
#[account(mut)]
operator: Signer<'info>,
- programs/usdc-pool-program/src/contexts/resolve_batch_withdraw.rs
#[derive(Accounts)]
pub struct ResolveBatchWithdraw<'info> {
#[account(mut)]
operator: Signer<'info>,
- programs/usdc-pool-program/src/contexts/collect_fee.rs
#[derive(Accounts)]
pub struct CollectFee<'info> {
#[account(mut)]
operator: Signer<'info>,
It is recommended to add a more granular access-control mechanism to mission-critical functions, which are currently relying on solely in the Operator
account.
Consider utilizing a Multi-signature wallet for the Operator
account. Alternatively, require more than a single signature for the mentioned instructions.
ACKNOWLEDGED: The Solayer team has acknowledged the issue.
// Informational
The usage of msg!
is usually advisable during tests, and will incur in additional computational budget when the instruction is processed in Mainnet.
In both resolve_batch_deposit
and resolve_batch_withdraw
instructions, it was observed logging utilizing msg!
.
- programs/usdc-pool-program/src/contexts/resolve_batch_deposit.rs
msg!("expected_amount {:?}", expected_amount);
msg!("rwa_token_amount {:?}", rwa_token_amount);
msg!("susd_mint_amount {:?}", susd_mint_amount);
- programs/usdc-pool-program/src/contexts/resolve_batch_withdraw.rs
msg!("expected_amount {:?}", expected_amount);
msg!("usdc_withdraw_amount {:?}", usdc_withdraw_amount);
Consider removing msg!
logging before mainnet deployment for enhanced computational budget efficiency.
ACKNOWLEDGED: The Solayer team has acknowledged the issue.
// Informational
The existing implementation of the set_operator
and set_rate_authority
instructions employ an one-step procedure for authority delegation, which presents a security concern. This method lacks a safeguard against inadvertent delegations to undesired accounts.
- programs/usdc-pool-program/src/contexts/set_operator.rs
#[derive(Accounts)]
pub struct SetOperator<'info> {
#[account(mut)]
manager: Signer<'info>,
#[account(
mut,
constraint = new_operator.key() != pool.operator.key()
)]
new_operator: Signer<'info>,
#[account(
mut,
has_one = manager,
seeds = [b"pool", USDC_MINT.as_ref(), pool.susd_mint.as_ref()],
bump = pool.bump
)]
pool: Account<'info, Pool>,
}
impl<'info> SetOperator<'info> {
pub fn set_operator(&mut self) -> Result<()> {
self.pool.operator = self.new_operator.key();
Ok(())
}
}
- programs/usdc-pool-program/src/contexts/set_rate_authority.rs
#[derive(Accounts)]
pub struct SetRateAuthority<'info> {
#[account(mut)]
manager: Signer<'info>,
#[account(
mut,
constraint = new_rate_authority.key() != pool.rate_authority.key()
)]
/// CHECK: The new rate authority account
new_rate_authority: AccountInfo<'info>,
#[account(
mut,
has_one = manager,
seeds = [b"pool", USDC_MINT.as_ref(), pool.susd_mint.as_ref()],
bump = pool.bump
)]
pool: Account<'info, Pool>,
}
impl<'info> SetRateAuthority<'info> {
pub fn set_rate_authority(&mut self) -> Result<()> {
self.pool.rate_authority = self.new_rate_authority.key();
Ok(())
}
}
To resolve this issue, it is advisable to establish a multi-step process for authority
transfer (being rate_authority
or new_operator
), thereby enhancing the security of the operation. The current authority
would first propose a new candidate authority
, who would then need to formally accept the role.
This process would be structured as follows:
Proposal by Current Authority: The current signer proposes a new candidate signer. This action updates the candidate_authority
field in the account's state. This step assumes the prior creation of an additional field candidate_authority
the state.
Acceptance by New Authority: The proposed candidate_authority
formally accepts the role. This step transfers the authority
status from the current authority
to the candidate_authority
.
ACKNOWLEDGED: The Solayer team has acknowledged the issue.
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. All vulnerabilities shown here were already disclosed in the above report. However, 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.
Cargo Audit Results
ID | Crate | Desccription |
---|---|---|
RUSTSEC-2022-0093 | ed25519-dalek | Double Public Key Signing Function Oracle Attack on |
RUSTSEC-2024-0344 | curve25519-dalek | Timing variability in |
RUSTSEC-2021-0145 | atty | Potential unaligned read |
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