Staking Contracts - Aria Protocol


Prepared by:

Halborn Logo

HALBORN

Last Updated 05/30/2025

Date of Engagement: May 14th, 2025 - May 16th, 2025

Summary

100% of all REPORTED Findings have been addressed

All findings

9

Critical

0

High

0

Medium

2

Low

2

Informational

5


1. Introduction

Aria Protocol engaged Halborn to conduct a security assessment on their smart contracts beginning on May 14th, 2025 and ending on May 16th, 2025. The security assessment was scoped to the smart contracts provided to Halborn. Commit hashes and further details can be found in the Scope section of this report.


The Aria Protocol codebase in scope mainly consists of smart contracts implementing a staking mechanism for RWIP tokens with time-locked staking tickets and KYC verification for unstaking operations.

2. Assessment Summary

Halborn was provided 3 days for the engagement and assigned 1 full-time security engineer to review the security of the smart contracts in scope. The engineer is a blockchain and smart contract security expert with advanced penetration testing and smart contract hacking skills, and deep knowledge of multiple blockchain protocols.


The purpose of the assessment is to:

    • Identify potential security issues within the smart contracts.

    • Ensure that smart contract functionality operates as intended.


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

    • Include a nonce in the signed message that increments with each use.

    • Replace the instance of _mint() with _safeMint() in the contract implementation to ensure recipients can properly handle ERC721 tokens.

    • Replace the current signature verification with OpenZeppelin's ECDSA library which includes malleability protection.

    • Add validation to ensure that staking amounts are greater than zero.


All addressed findings have been consolidated and incorporated into version v1.0.9, available in the following commit: https://github.com/AriaProtocol/main-contracts/tree/c0a926fa1725b1bc03cca1bd8d70a5633ddcc061.

3. Test Approach and 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 this 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.

    • Smart contract manual code review and walkthrough to identify any logic issue.

    • Thorough assessment of safety and usage of critical Solidity variables and functions in scope that could led to arithmetic related vulnerabilities.

    • Local testing with custom scripts (Foundry).

    • Fork testing against main networks (Foundry).

    • Static analysis of security for scoped contract, and imported functions (Slither).


4. Static Analysis Report

4.1 Description

Halborn used automated testing techniques to enhance the coverage of certain areas of the smart contracts in scope. Among the tools used was Slither, a Solidity static analysis framework. After Halborn verified the smart contracts in the repository and was able to compile them correctly into their abis and binary format, Slither was run against the contracts. This tool can statically verify mathematical relationships between Solidity variables to detect invalid or inconsistent usage of the contracts' APIs across the entire code-base.


The security team assessed all findings identified by the Slither software, however, findings with related to external dependencies are not included in the below results for the sake of report readability.

4.2 Output

The findings obtained as a result of the Slither scan were reviewed, and some were not included in the report because they were determined as false positives.



5. RISK METHODOLOGY

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

5.1 EXPLOITABILITY

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

E=meE = \prod m_e

5.2 IMPACT

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

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

5.3 SEVERITY COEFFICIENT

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

C=rsC = rs

The Vulnerability Severity Score SS is obtained by:

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

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

6. SCOPE

Files and Repository
(a) Repository: main-contracts
(b) Assessed Commit ID: 2800755
(c) Items in scope:
  • contracts/rwip/staking/RWIPStaking.sol
  • contracts/rwip/staking/StakedRWIP.sol
  • contracts/rwip/staking/StakingTicket.sol
Out-of-Scope: Third party dependencies and economic attacks.
Remediation Commit ID:
Out-of-Scope: New features/implementations after the remediation commit IDs.

7. Assessment Summary & Findings Overview

Critical

0

High

0

Medium

2

Low

2

Informational

5

Security analysisRisk levelRemediation Date
Signature replay during unstakingMediumSolved - 05/19/2025
Unsafe token minting can lead to locked assetsMediumSolved - 05/23/2025
Signature verification vulnerable to malleabilityLowSolved - 05/23/2025
Zero amount stake allows for unlimited ticket token mintingLowSolved - 05/28/2025
Missing input validationInformationalAcknowledged - 05/28/2025
Missing eventsInformationalPartially Solved - 05/28/2025
Typo in function nameInformationalSolved - 05/28/2025
Use of revert strings instead of custom errorsInformationalSolved - 05/24/2025
Lack of named mappingInformationalAcknowledged - 05/28/2025

8. Findings & Tech Details

8.1 Signature replay during unstaking

//

Medium

Description

The _checkKYCSignature() function in the RWIPStaking contract is vulnerable to signature replay attacks. When a user without an established KYC status calls the unstake() function, they must provide a signature from an authorized KYC signer. However, the contract fails to implement any mechanism to prevent reuse of valid signatures.


The signature verification logic only checks if the signature was created by an authorized KYC signer and that it corresponds to the user's address:

 if (!addressHasKYC[msg.sender]) _checkKYCSignature(msg.sender, _kycSignature);

function _checkKYCSignature(address _user, bytes memory _signature) internal view {
    // Split signature
    require(_signature.length == 65, "invalid signature length");

    bytes32 r;
    bytes32 s;
    uint8 v;

    assembly {
        r := mload(add(_signature, 32))
        s := mload(add(_signature, 64))
        v := byte(0, mload(add(_signature, 96)))
    }

    bytes32 messageHash = keccak256(abi.encodePacked(KYC_MESSAGE, _user));
    bytes32 ethSignedMessageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));

    address recoveredAddress = ecrecover(ethSignedMessageHash, v, r, s);

    require(hasRole(KYC_SIGNER_ROLE, recoveredAddress), "Not Auth");
}

The signature contains no unique identifier, like a nonce, to limit its validity period. This enables a valid signature to be reused multiple times across different transactions.


If KYC requirements change or a user's KYC status should be revoked, the existing signatures would continue to work, undermining the compliance controls of the protocol.

Proof of Concept

In the following scenario, Alice is able to re-use her signature:


function test_signatureReplay() public {
  // Setup: Define amount to stake
  uint256 amount = 100 ether;
  uint256 stakingHoldPeriod = 7 days;

  console.log("===> Setting up with stake amount:", amount / 1 ether, "RWIP");

  // First stake and redeem to get stakedRWIP tokens
  vm.startPrank(alice);
  rwipToken.approve(address(rwipStaking), amount);
  rwipStaking.stake(amount, stakingHoldPeriod);
  uint256 ticketId = 0;

  // Advance time past the holding period
  vm.warp(block.timestamp + stakingHoldPeriod + 1);

  // Redeem the ticket to get stakedRWIP tokens
  rwipStaking.redeemTicket(ticketId);
  console.log("Alice stakedRWIP balance after redeem:", stakedRwip.balanceOf(alice) / 1 ether);
  vm.stopPrank();

  // Make sure the RWIPStaking contract has RWIP tokens to return
  deal(address(rwipToken), address(rwipStaking), amount * 2); // Enough for two unstakes

  // Ensure alice does NOT have KYC status
  vm.startPrank(signerAdmin);
  rwipStaking.setKYCStatusForAddress(alice, false);
  vm.stopPrank();
  console.log("Alice KYC status set to FALSE - requires signature");

  // Create and sign the KYC message for alice
  bytes32 messageHash = keccak256(abi.encodePacked("KYC ATTESTATION FOR: ", alice));
  bytes32 ethSignedMessageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));

  // Sign the message with our signer's private key
  (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, ethSignedMessageHash);
  bytes memory signature = abi.encodePacked(r, s, v);

  // First unstake operation - use half the tokens
  uint256 halfAmount = amount / 2;
  console.log("===> First Unstake: Using", halfAmount / 1 ether, "stakedRWIP");

  vm.startPrank(alice);
  stakedRwip.approve(address(rwipStaking), halfAmount);
  rwipStaking.unstake(halfAmount, signature);

  // Verify first unstake was successful
  console.log("First unstake successful - remaining stakedRWIP:", stakedRwip.balanceOf(alice) / 1 ether);

  // VULNERABILITY: Now use the same signature for another unstake
  console.log("===> Second Unstake: Using SAME signature");

  stakedRwip.approve(address(rwipStaking), halfAmount);
  rwipStaking.unstake(halfAmount, signature);

  // Verify second unstake was also successful, proving the signature replay vulnerability
  console.log("Second unstake successful - remaining stakedRWIP:", stakedRwip.balanceOf(alice) / 1 ether);
  console.log("===> Signature replay vulnerability confirmed");
  vm.stopPrank();
}


BVSS
Recommendation

Implement a mechanism to prevent signature replay, for example:

  1. Include a nonce in the signed message that increments with each use.

  2. Add an expiration timestamp to the signature.

  3. Track used signatures in contract storage.


Remediation Comment

SOLVED: The Aria Protocol team solved this finding in commit 4db3df6 by following the mentioned recommendation.

Remediation Hash
References

8.2 Unsafe token minting can lead to locked assets

//

Medium

Description

The StakingTicket contract uses the standard ERC721 _mint() function instead of _safeMint() when creating new staking tickets in the mint() function:

_mint(_to, newTokenId);

Unlike _safeMint(), the _mint() function does not verify whether the recipient can handle ERC721 tokens when the recipient is a contract.


This implementation allows staking tickets to be minted to contract addresses that do not implement the IERC721Receiver.onERC721Received() function, potentially resulting in tickets being permanently locked and inaccessible. When this happens, the collateral represented by these tickets (RWIP tokens) would also be effectively lost.

BVSS
Recommendation

Replace the instance of _mint() with _safeMint() in the contract implementation to ensure recipients can properly handle ERC721 tokens.

Remediation Comment

SOLVED: The Aria Protocol team solved this finding in commit e0854d6 by following the mentioned recommendation.

Remediation Hash
References

8.3 Signature verification vulnerable to malleability

//

Low

Description

The RWIPStaking contract uses raw ecrecover for signature verification without protection against signature malleability. This allows multiple valid signatures to be created for the same message, potentially undermining security mechanisms that rely on signature uniqueness.


When verifying KYC signatures in the _checkKYCSignature function, the contract uses Ethereum's native ecrecover function:

address recoveredAddress = ecrecover(ethSignedMessageHash, v, r, s);
require(hasRole(KYC_SIGNER_ROLE, recoveredAddress), "Not Auth");

This implementation does not enforce signature canonicalization per EIP-2, which requires s values to be in the lower half of the curve. Due to properties of elliptic curve cryptography, for any valid signature (r, s, v), another valid signature exists with parameters (r, curve_order - s, flipped v), where flipped v toggles between 27 and 28.


This vulnerability could:


  1. Undermine any future replay protection based on signature uniqueness.

  2. Allow the same KYC attestation to be processed with different signature representations.

  3. Cause inconsistencies in systems that track or index KYC attestations by signature.


While not immediately exploitable for fund theft, this vulnerability represents a deviation from best practices and could interact with other protocol features that assume signature uniqueness.

Proof of Concept

In the following scenario two different signatures can recover to same signer:

function test_signatureMalleability() public {
  // Setup: Define amount to stake
  uint256 amount = 100 ether;
  uint256 stakingHoldPeriod = 7 days;

  console.log("===> Setting up test for signature malleability");

  // First stake and redeem to get stakedRWIP tokens
  vm.startPrank(alice);
  rwipToken.approve(address(rwipStaking), amount);
  rwipStaking.stake(amount, stakingHoldPeriod);

  vm.warp(block.timestamp + stakingHoldPeriod + 1);
  rwipStaking.redeemTicket(0);
  vm.stopPrank();

  // Ensure the contract has RWIP tokens to return
  deal(address(rwipToken), address(rwipStaking), amount * 2);

  // Ensure alice does NOT have KYC status
  vm.prank(signerAdmin);
  rwipStaking.setKYCStatusForAddress(alice, false);

  // Create message hash for KYC attestation
  bytes32 messageHash = keccak256(abi.encodePacked("KYC ATTESTATION FOR: ", alice));
  bytes32 ethSignedMessageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));

  // Get original signature
  (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPrivateKey, ethSignedMessageHash);

  // Create malleated signature (with flipped s)
  // secp256k1 curve order n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
  bytes32 curveOrder = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
  bytes32 malleatedS = bytes32(uint256(curveOrder) - uint256(s));
  uint8 malleatedV = v == 27 ? 28 : 27; // Flip v between 27 and 28

  // Pack both signatures
  bytes memory originalSig = abi.encodePacked(r, s, v);
  bytes memory malleatedSig = abi.encodePacked(r, malleatedS, malleatedV);

  // Log the complete signatures (in hex)
  console.log("Original signature:");
  console.logBytes(originalSig);
  console.log("Malleated signature:");
  console.logBytes(malleatedSig);
  console.log("The signatures above are different but validate to the same signer");

  // Verify both signatures recover to the same address
  address recovered1 = ecrecover(ethSignedMessageHash, v, r, s);
  address recovered2 = ecrecover(ethSignedMessageHash, malleatedV, r, malleatedS);
  console.log("Both signatures recover to:", recovered1);
  assert(recovered1 == recovered2);
  assert(keccak256(originalSig) != keccak256(malleatedSig)); // Confirm signatures are different

  // Use both signatures to unstake in two separate transactions
  uint256 unstakeAmount = amount / 2;

  // First unstake with original signature
  vm.startPrank(alice);
  stakedRwip.approve(address(rwipStaking), unstakeAmount);
  rwipStaking.unstake(unstakeAmount, originalSig);

  // Second unstake with malleated signature
  stakedRwip.approve(address(rwipStaking), unstakeAmount);
  rwipStaking.unstake(unstakeAmount, malleatedSig);
  vm.stopPrank();
}


BVSS
Recommendation

Replace the current signature verification with OpenZeppelin's ECDSA library, which includes malleability protection:

address recoveredAddress = ECDSA.recover(ethSignedMessageHash, _signature);

Remediation Comment

SOLVED: The Aria Protocol team solved this finding in commit 74ca8dd by following the mentioned recommendation.

Remediation Hash
References

8.4 Zero amount stake allows for unlimited ticket token minting

//

Low

Description

The stake() function in the RWIPStaking contract does not validate that the staking amount is greater than zero, allowing users to mint unlimited staking tickets without actually staking any meaningful collateral.


The stake() function in the RWIPStaking contract lacks validation to ensure that _amount is greater than zero:

function stake(uint256 _amount, uint256 _stakingHoldPeriod) external {
    if (_stakingHoldPeriod < minStakingHoldPeriod) revert StakingHoldPeriodTooLow();

    rwipToken.safeTransferFrom(msg.sender, address(stakingTicket), _amount);
    uint256 stakingTicketId = stakingTicket.mint(msg.sender, _amount, _stakingHoldPeriod);
    emit RWIPStaked(msg.sender, stakingTicketId, _amount, _stakingHoldPeriod);
}

This allows users to call stake(0, validHoldPeriod) repeatedly, which:


  1. Transfers 0 RWIP tokens (which succeeds, as ERC20 allows zero-amount transfers).

  2. Mints a new staking ticket with 0 collateral.

  3. Results in a valid ERC721 token being minted to the user.


While these tickets would each redeem for 0 stRWIP tokens, they remain valid NFTs that could potentially be leveraged for unintended purposes, potentially manipulating systems that use ticket token quantities for calculations.


Proof of Concept

In the following scenario, the attacker can mint 100 NFT tokens without having to transfer any ERC20 tokens to the contract:

function test_unlimitedMintWithZeroAmount() public {
  vm.startPrank(attacker);
  for (uint256 i = 0; i < 100; i++) rwipStaking.stake(0, 1 days);
  vm.stopPrank();
  assertEq(stakingTicket.balanceOf(attacker), 100);
}


BVSS
Recommendation

Add validation to ensure that staking amounts are greater than zero.

Remediation Comment

SOLVED: The Aria Protocol team solved this finding in commit 612586a by following the mentioned recommendation.

Remediation Hash
References

8.5 Missing input validation

//

Informational

Description

Throughout the codebase, there are several instances where input values are assigned without proper validation. For example, ensuring that an input address is not the zero address.


Failing to validate inputs before assigning them to state variables can lead to unexpected system behavior or even complete failure.


Instances of this issue include:


  • In StakingTicket.initialize(), _rwipToken and _stakingContract are not checked against the zero address before assignment.


  • In StakingTicket.setRWIPToken(), _rwipToken is not validated to ensure it's not the zero address before assignment.


  • In StakingTicket.setStakingContract(), _stakingContract is not validated to ensure it's not the zero address before assignment.


  • In RWIPStaking.initialize(), _rwipToken, _stakedRWIPToken, _stakingTicket, and _kycSignerAdmin are not checked against the zero address before assignment, and the _minStakingHoldPeriod is not verified to fall within a valid threshold.


  • In RWIPStaking.unstake(), there's no validation to ensure that _amount is greater than zero. This allows users to unstake 0 tokens, which would waste gas and emit misleading events with zero values.


  • In RWIPStaking.setRWIPToken(), _rwipToken is not checked against the zero address before assignment.


  • In RWIPStaking.setMinStakingHoldPeriod(), _minStakingHoldPeriod is not verified to fall within a valid threshold.


  • In RWIPStaking.setStakedRWIPToken(), _stakedRWIPToken is not checked against the zero address before assignment.


  • In RWIPStaking.setStakingTicket(), _stakingTicket is not checked against the zero address before assignment.


BVSS
Recommendation

Add proper validation to ensure that the input values are within expected ranges and that addresses are not the zero address. This will help prevent unexpected behavior and improve the overall robustness of the code.

Remediation Comment

ACKNOWLEDGED: The Aria Protocol team made a business decision to acknowledge this finding and not alter the contracts.

References

8.6 Missing events

//

Informational

Description

Throughout the contracts in scope, there are several instances where administrative functions change contract state by modifying core state variables without them being reflected in event emissions. The absence of events may hamper effective state tracking in off-chain monitoring systems.


Instances of this issue can be found in:

  • StakingTicket.setRWIPToken()

  • StakingTicket.setStakingContract()

  • RWIPStaking.withdraw()

  • RWIPStaking.setKYCStatusForAddress()

  • RWIPStaking.setMinStakingHoldPeriod()

  • RWIPStaking.setRWIPToken()

  • RWIPStaking.setStakedRWIPToken()

  • RWIPStaking.setStakingTicket()


BVSS
Recommendation

Emit events for all state changes that occur as a result of administrative functions to facilitate off-chain monitoring of the system.

Remediation Comment

PARTIALLY SOLVED: The Aria Protocol team partially solved this finding in commit bfe98bdbadcbfb33b6e4affcbe9b191bb9f9a382 by following the mentioned recommendation and adding events to some of the aforementioned functions.

Remediation Hash

8.7 Typo in function name

//

Informational

Description

In the StakedRWIP contract, there is a typo, where the function initialize() is misspelled as intialize().


This typo may affect the functionality of the code and initialization of the contract, depending on the framework used for deployment.

BVSS
Recommendation

It is recommended to fix the typo to improve the readability of the codebase and facilitate proper initialization.

Remediation Comment

SOLVED: The Aria Protocol team solved this finding in commit 0c53aaf by following the mentioned recommendation.

Remediation Hash
References

8.8 Use of revert strings instead of custom errors

//

Informational

Description

Throughout the files in scope, there are several instances where revert strings are used over custom errors.


In Solidity development, replacing hard-coded revert message strings with the Error() syntax is an optimization strategy that can significantly reduce gas costs. Hard-coded strings, stored on the blockchain, increase the size and cost of deploying and executing contracts.


The Error() syntax allows for the definition of reusable, parameterized custom errors, leading to a more efficient use of storage and reduced gas consumption. This approach not only optimizes gas usage during deployment and interaction with the contract but also enhances code maintainability and readability by providing clearer, context-specific error information.

BVSS
Recommendation

Consider replacing all revert strings with custom errors. For example:

error ConditionNotMet();

if (!condition) revert ConditionNotMet();

or starting from Solidity 0.8.27 :

require(condition, ConditionNotMet());

For more references, see here and here.

Remediation Comment

SOLVED: The Aria Protocol team solved this finding in commit 4db3df6 by following the mentioned recommendation.

Remediation Hash
References

8.9 Lack of named mapping

//

Informational

Description

The addressHasKYC mapping in unnamed despite using a Solidity version that supports named mappings.


Named mappings improve code readability and self-documentation by explicitly stating their purpose.

BVSS
Recommendation

Consider refactoring the mapping to use named arguments, which will enhance code readability and make the purpose of the mapping more explicit.

Remediation Comment

ACKNOWLEDGED: The Aria Protocol team made a business decision to acknowledge this finding and not alter the contracts.

References

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 2025. All rights reserved.