Halborn Logo

icon

InceptionLRT Bridge - Tagus Labs


Prepared by:

Halborn Logo

HALBORN

Last Updated 08/05/2024

Date of Engagement by: April 22nd, 2024 - April 29th, 2024

Summary

100% of all REPORTED Findings have been addressed

All findings

15

Critical

0

High

0

Medium

0

Low

5

Informational

10


1. Introduction

Tagus Labs engaged Halborn to conduct a security assessment on their smart contracts beginning on 04-22-2024 and ending on 04-29-2024. The security assessment was scoped to the smart contracts provided in the https://github.com/inceptionlrt/bridge/tree/feat/extend-bridge-with-xerc20 GitHub repository. Commit hashes and further details can be found in the Scope section of this report.

2. Assessment Summary

Halborn was provided 1 week 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 Tagus Labs team. The main identified issues are:

    • Lack of checks for deposits and withdrawals recipients may allow for tokens lost in XERC20Lockbox contract.

    • Low limit amount for a bridge will result in a rate per second equal to zero.

    • Possible denial of service for bridges when high limits are set.

3. Test Approach and Methodology

Halborn performed a combination of manual 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 the contracts' solidity code 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 and purpose.

    • Smart contract manual code review and walk-through.

    • Manual assessment of use and safety for the critical Solidity variables and functions in scope to identify any arithmetic-related vulnerability classes.

    • Manual testing with custom scripts (Foundry).

    • Static Analysis of security for scoped contract, and imported functions.

3.1 Out-of-scope

    • External libraries and financial-related attacks.

    • New features/implementations after/within the remediation commit IDs.

    • The following files, while they affect the in-scope code, were not specifically reviewed as part of the scope and are assumed to not contain any issues:

      • lib/EthereumVerifier.sol

      • lib/ProofParser.sol

      • lib/CallDataRLPReader.sol

      • lib/Utils.sol

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

4.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:
EXPLOITABILIY 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

4.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}

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

5. SCOPE

Files and Repository
(a) Repository: bridge
(b) Assessed Commit ID: 8281a3c
(c) Items in scope:
  • contracts/bridge/InceptionBridge.sol
  • contracts/XERC20/XERC20.sol
  • contracts/XERC20/XERC20Lockbox.sol
↓ Expand ↓
Out-of-Scope: lib/CallDataRLPReader.sol, lib/EthereumVerifier.sol, lib/ProofParser.sol, lib/Utils.sol
Remediation Commit ID:
Out-of-Scope: New features/implementations after the remediation commit IDs.

6. Assessment Summary & Findings Overview

Critical

0

High

0

Medium

0

Low

5

Informational

10

Security analysisRisk levelRemediation Date
Lack of checks for deposits and withdrawals recipients may allow for tokens lost in XERC20Lockbox contractLowSolved - 04/29/2024
Low limit amount for a bridge will result in a rate per second equal to zeroLowSolved - 04/29/2024
Possible denial of service for bridges when high limits are setLowRisk Accepted
Centralization risks due to privileged access by ownerLowRisk Accepted
Potential risks in single-step transfer of ownershipLowRisk Accepted
Lack of check for zero address when adding destinationInformationalSolved - 04/29/2024
Lack of check for deposits and withdrawals with zero amountInformationalAcknowledged
Public functions not called within the contract can be made externalInformationalSolved - 04/29/2024
Use of post-increment operatorInformationalSolved - 04/29/2024
Unused importInformationalSolved - 04/29/2024
Missing error descriptionInformationalSolved - 04/29/2024
Unlocked pragma compilersInformationalAcknowledged
PUSH0 is not supported by all chainsInformationalAcknowledged
InceptionBridge contract assumes uniform decimal places across tokensInformationalAcknowledged
Use of full file importsInformationalAcknowledged

7. Findings & Tech Details

7.1 Lack of checks for deposits and withdrawals recipients may allow for tokens lost in XERC20Lockbox contract

// Low

Description

In the XERC20Lockbox contract, the functions that allow users to make deposits and withdrawals to an arbitrary recipient do not check if the recipient is the XERC20Lockbox contract itself. This may end up in self-transfers that result in tokens being locked in the contract.

Consider the following scenarios:

Scenario A

The function depositNativeTo() allows users to make native deposits and transfer the corresponding tokens to an arbitrary receiver. If the _to address is set (intentionally or accidentally) to the XERC20Lockbox address, it allows for XERC20 tokens to be deposited to the XERC20Lockbox contract.

  function depositNativeTo(address _to) public payable {
    if (!IS_NATIVE) revert IXERC20Lockbox_NotNative();
    _deposit(_to, msg.value);
  }
  function _deposit(address _to, uint256 _amount) internal {
    if (!IS_NATIVE) ERC20.safeTransferFrom(msg.sender, address(this), _amount);
    XERC20.mint(_to, _amount);
    emit Deposit(_to, _amount);
  }

Scenario B

Similarly, the function depositTo allows for users to make token deposits to an arbitrary recipient. In this case, the result will be the same as the previous scenario, with additional ERC20 tokens being deposited into the contract.

  function depositTo(address _to, uint256 _amount) external {
    if (IS_NATIVE) revert IXERC20Lockbox_Native();
    _deposit(_to, _amount);
  }

Scenario C

Similarly, the withdrawTo function may result in the same outcome with additional steps, leaving XERC20 tokens deposited to the contract.

  function _withdraw(address _to, uint256 _amount) internal {
    XERC20.burn(msg.sender, _amount);
    if (IS_NATIVE) {
      (bool _success, ) = payable(_to).call{value: _amount}("");
      if (!_success) revert IXERC20Lockbox_WithdrawFailed();
    } else {
      ERC20.safeTransfer(_to, _amount);
    }
    emit Withdraw(_to, _amount);
  }

If the _to address is set to the XERC20Lockbox address and the IS_NATIVE condition is met, execution will reach the code to transfer native assets:

  (bool _success, ) = payable(_to).call{value: _amount}("");
  if (!_success) revert IXERC20Lockbox_WithdrawFailed();

This code will effectively make a native transfer to the lockbox contract, which in turn will activate the execution of the local receive() function, executing depositNative(). This will make deposits on behalf of the XERC20Lockbox (being its address the msg.sender) and contract and XERC20 tokens will be minted to it.

  receive() external payable {
    depositNative();
  }

Given these scenarios, the XERC20 tokens will remain locked in the contract as there is no functionality in XERC20Lockbox to retrieve them.

Proof of Concept

Scenario A:

  function test_depositNativeToLockbox(address receiver, uint256 amount) public {
    vm.assume(receiver != address(0));
    vm.assume(amount != 0);
    test_deployLockboxNative();

    receiver = address(lockbox);
    vm.deal(depositor, amount);
    vm.startPrank(depositor);

    uint256 balanceBefore = xerc20.balanceOf(receiver);
    lockbox.depositNativeTo{value: amount}(receiver);
    uint256 balanceAfter = xerc20.balanceOf(receiver);
    assertEq(balanceBefore + amount, balanceAfter);
    vm.stopPrank();
  }
Scenario A

Scenario B:

  function test_depositTokenToLockbox(address receiver, uint256 amount) public {
    vm.assume(receiver != address(0));
    vm.assume(amount != 0);
    test_deployLockboxNotNative();

    receiver = address(lockbox);
    deal(address(mockToken), depositor, amount);
    vm.startPrank(depositor);
    mockToken.approve(address(lockbox), amount);

    uint256 balanceBefore = mockToken.balanceOf(receiver);
    lockbox.depositTo(receiver, amount);
    uint256 balanceAfter = mockToken.balanceOf(receiver);
    assertEq(balanceBefore + amount, balanceAfter);
    vm.stopPrank();
  }
Scenario B
  function test_withdrawToLockboxNative(uint256 withdrawAmount) public {
    test_deployLockboxNative();
    withdrawAmount = bound(withdrawAmount, 1, type(uint).max);
    address receiver = address(lockbox); //set lockbox as receiver

    vm.deal(receiver, withdrawAmount);

    vm.prank(address(lockbox));
    xerc20.mint(depositor, withdrawAmount);

    uint256 balanceXerc20Before = xerc20.balanceOf(receiver);
    vm.startPrank(depositor);

    lockbox.withdrawTo(receiver, withdrawAmount);

    uint256 balanceXerc20After = xerc20.balanceOf(receiver);
    assertEq(balanceXerc20Before + withdrawAmount, balanceXerc20After);
  }
Scenario C
BVSS
Recommendation

Consider adding checks on the aforementioned functions to ensure that in no case the recipient of the transfers is the XERC20Lockbox contract itself.

Remediation Plan

SOLVED: The Tagus Labs team has addressed the finding in commit a98d3e2 by following the mentioned recommendation of adding a check to verify that the receiver is not the XERC20Lockbox contract itself.

Remediation Hash
References
XERC20Lockbox.sol#L52-L55
XERC20Lockbox.sol#L62-L65
XERC20Lockbox.sol#L71-L74
XERC20Lockbox.sol#L89-L91
XERC20Lockbox.sol#L98-L107
XERC20Lockbox.sol#L114-L120
XERC20Lockbox.sol#L125-L127

7.2 Low limit amount for a bridge will result in a rate per second equal to zero

// Low

Description

In the changeBurnerLimit and changeMintingLimit functions, which are executed via setBridgeLimits(), any limit set below 86400 (equal to 1 days in Solidity) will result in a zero rate per second, affecting the possibility of dynamic limit within the DURATION threshold. This may occur because the rate per second is calculated as following in the aforementioned functions:

bridges[_bridge].minterParams.ratePerSecond = _limit / _DURATION;
bridges[_bridge].burnerParams.ratePerSecond = _limit / _DURATION;

where uint256 private constant _DURATION = 1 days.

This may result in a static limit for the specified bridge, as with a rate per second with 0 value will not change the _calculatedLimit value in the following code execution whenever the _getCurrentLimit() function is called:

uint256 _timePassed = block.timestamp - _timestamp;
uint256 _calculatedLimit = _limit + (_timePassed * _ratePerSecond);
_limit = _calculatedLimit > _maxLimit ? _maxLimit : _calculatedLimit;
BVSS
Recommendation

It is recommended to set a minimum threshold when invoking setBridgeLimits() or, alternatively, incorporate a validation step to confirm that ratePerSecond is not set to zero.

Remediation Plan

SOLVED: The Tagus Labs team has addressed the finding in commit a98d3e2 by following the mentioned recommendation of adding a check for a minimum threshold.

Remediation Hash
References
XERC20.sol#L82-L90

7.3 Possible denial of service for bridges when high limits are set

// Low

Description

When using Solidity versions 0.8.0 and above, the compiler natively handles overflows and underflows by reverting the transaction if these issues occur. In the XERC20 contract, specifically in the _getCurrentLimit() function, there's a potential for an unhandled overflow, which could cause the transaction to revert when calculating the current limit:

uint256 _calculatedLimit = _limit + (_timePassed * _ratePerSecond);

If the multiplication operation between _timePassed and _ratePerSecond results in a value greater than type(uint256).max, the transaction will revert. Similarly, if the result of the addition _limit and (_timePassed * _ratePerSecond) is greater than type(uint256).max, the transaction will also revert, effectively denying service for the bridge attempting to mint or burn XERC20 tokens under these circumstances.

Proof of Concept

For example, even though the limit set is very high, a bridge is not able to mint 2 tokens due to the overflow occuring when calculating the new limit.

  function test_mintVeryHighLimitDOS(address _bridge, uint256 _burningLimit, uint256 amount) public {
    vm.assume(_burningLimit != 0);
    vm.assume(_bridge != address(0));
    amount = bound(amount, 1, type(uint256).max);
    test_setBridgeLimits(_bridge, type(uint256).max, _burningLimit);
    vm.startPrank(_bridge);

    uint256 currentLimitBefore = xerc20.mintingCurrentLimitOf(_bridge);
    uint256 maxLimitBefore = xerc20.mintingMaxLimitOf(_bridge);

    xerc20.mint(_bridge, 1);
    assertEq(xerc20.balanceOf(_bridge), 1);
    skip(1);
    vm.expectRevert();
    xerc20.mint(_bridge, 1);
  }
BVSS
Recommendation

It is recommended to add a threshold in the setBridgeLimits() function to ensure that no calculated limit will exceed the maximum value of a uint256 variable, allowing bridges to mint and burn when needed.

Remediation Plan

RISK ACCEPTED: The Tagus Labs team made a business decision to accept the risk of this finding and not alter the contracts, stating:

This bug is next to impossible due to the range of values we’re working with in this case. In particular, we’re dealing with timestamps vs. uint256. It turns into impossible time limits.

References
XERC20.sol#L254

7.4 Centralization risks due to privileged access by owner

// Low

Description

Several contracts in scope have owners with privileged rights to perform administrative tasks and need to be trusted to not perform malicious updates.

The administrative functions enable the owner to add and remove bridges, adjust their limits, set caps and their durations, designate token destinations, configure contracts, and pause or unpause the bridge.

BVSS
Recommendation

It is recommended to implement a role-based access control mechanism to allow multiple entities to perform admin tasks and limit powers within the system to users according to their roles, effectively reducing centralization risks.

Remediation Plan

RISK ACCEPTED: The Tagus Labs team made a business decision to accept the risk of this finding and not alter the contracts, stating:

We’re aware of that, and therefore we use a 3-5 multisig address as the owner.

7.5 Potential risks in single-step transfer of ownership

// Low

Description

The current implementation of the InceptionBridge contract employs a single-step ownership transfer mechanism, transferring ownership directly to another account in one transaction. This is achieved through the inheritance of OpenZeppelin's OwnableUpgradeable contract. Similarly, the XERC20 contract inherits from OpenZeppelin's Ownable contract.

This approach taken for both contracts mentioned does not align with best practices regarding security measures, as the address to which the ownership is transferred should be verified to be active or be willing to act as the owner.

BVSS
Recommendation

It is recommended to use OpenZeppelin's Ownable2Step or Ownable2StepUpgradeble contracts over the Ownable and OwnableUpgradeable currently in use. Alternatively, consider implementing similar two-step ownership transfer logic into the contract.

Remediation Plan

RISK ACCEPTED: The Tagus Labs team made a business decision to accept the risk of this finding and not alter the contracts.

References
XERC20/XERC20.sol#L9
InceptionBridge.sol#L27

7.6 Lack of check for zero address when adding destination

// Informational

Description

The _addDestination() function from the InceptionBridge contract has a check to verify that the _bridgeAddressByChainId mapping is properly set up and is not equivalent to address(0. However, it does not perform the same verification for the fromToken or the toToken input values.

This may incur in possible denial of service for addresses attempting to execute deposit() or withdraw().

BVSS
Recommendation

Add a check to ensure that neither the fromToken and toToken addresses are address(0).

Remediation Plan

SOLVED: The Tagus Labs team has addressed the finding in commit a98d3e2 by following the mentioned recommendation.

Remediation Hash
References
InceptionBridgeStorage.sol#L198-L221

7.7 Lack of check for deposits and withdrawals with zero amount

// Informational

Description

Throughout the codebase, there are multiple instances where the deposit and withdraw functions do not check if the amount to deposit or withdraw is zero. This may result in unnecessary gas consumption or unexpected behavior.

The affected functions are:

- deposit and withdraw functions in the InceptionBridge contract.

- deposit, depositTo, depositNative, depositNativeTo, withdraw and withdrawTo functions in the XERC20Lockbox contract.

Score
Recommendation

Add checks in the aforementioned functions to ensure that the amount to deposit or withdraw is greater than zero.

Remediation Plan

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

References
InceptionBridge.sol
XERC20Lockkbox.sol

7.8 Public functions not called within the contract can be made external

// Informational

Description

Throughout the codebase, there are multiple functions that are marked as public but are not used internally in the contract they are declared. This may result in unnecessary gas consumption.

The affected functions are:

- mint, burn, setLockbox, mintingMaxLimitOf, burningMaxLimitOf in the XERC20 contract.

- depositNativeTo in the XERC20Lockbox contract.

- getDeploymentCreate2Address in the BridgeFactory contract.

Score
Recommendation

Modify the aforementioned functions with the external visibility modifier.

Remediation Plan

SOLVED: The Tagus Labs team has addressed the finding in commit a98d3e2 by following the mentioned recommendation.

Remediation Hash

7.9 Use of post-increment operator

// Informational

Description

In the deposit function of the InceptionBridge contract, the post-increment operator is used to increment the _globalNonce variable:

_globalNonce++;

This is not the most gas efficient approach to incrementing a variable.

Score
Recommendation

Replace the post-increment operator with the pre-increment operator to reduce gas costs. Additionally, the piece of code can be executed inside an unchecked block, since realistically it is not possible for the _globalNonce to overflow in practice:

unchecked{
  ++_globalNonce;
}

Remediation Plan

SOLVED: The Tagus Labs team has addressed the finding in commit a98d3e2 by following the mentioned recommendation.

Remediation Hash
References
InceptionBridge.sol#L103

7.10 Unused import

// Informational

Description

In the InceptionBridge.sol file, OpenZeppelin's IERC20Metada.sol is imported but not used in the contract.

Score
Recommendation

Remove the unused import statement.

Remediation Plan

SOLVED: The Tagus Labs team has addressed the finding in commit a98d3e2 by removing the unused import file.

Remediation Hash
References
InceptionBridge.sol#L10

7.11 Missing error description

// Informational

Description

The InitializableTransparentUpgradeableProxy contract contains require statements that are missing error descriptions, particularly in the initialize() function:

require(_implementation() == address(0));

and the _requireZeroValue() function:

require(msg.value == 0);
Score
Recommendation

Use descriptive reason strings. Alternatively, it is recommended the use of custom errors to follow the project's error standardization, best practices and to improve gas efficiency.

Remediation Plan

SOLVED: The Tagus Labs team has addressed the finding in commit a98d3e2 by using descriptive reason strings.

Remediation Hash
References
InitializableTransparentUpgradeableProxy.sol#L23
InitializableTransparentUpgradeableProxy.sol#L164

7.12 Unlocked pragma compilers

// Informational

Description

To ensure stability, it's important that contracts are deployed with the same compiler version and flags used during development and testing. Locking the pragma helps to ensure that contracts do not accidentally get deployed using another pragma. For example, an outdated pragma version might introduce bugs that affect the contract system negatively.

Score
Recommendation

Lock the pragma version to the same version used during development and testing.

Remediation Plan

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

7.13 PUSH0 is not supported by all chains

// Informational

Description

The compiler for Solidity 0.8.20 switches the default target EVM version to Shanghai, which means that the generated bytecode will include PUSH0 opcodes. Be sure to select the appropriate EVM version in case you intend to deploy on a chain apart from mainnet like L2 chains that may not support PUSH0, otherwise deployment of your contracts will fail.

Score
Recommendation

Make sure to specify the target EVM version when using Solidity 0.8.20 and above, especially if deploying to L2 chains that may not support the PUSH0 opcode. Stay informed about the opcode support of different chains to ensure smooth deployment and compatibility.

Remediation Plan

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

7.14 InceptionBridge contract assumes uniform decimal places across tokens

// Informational

Description

The bridge contract operates under the assumption that all bridgeable tokens across supported chains have uniform decimal places. When tokens are deposited, the system records the quantity of tokens. Conversely, during withdrawals, it is assumed that the exact figure is used to mint tokens for users. If the referenced tokens have different decimal scaling, it can lead to discrepancies between these values.

On the other hand, it is understood that since the bridgeable tokens are managed internally, it is expected that they are all issued with the same number of decimal places, which would make it unlikely to impact the current codebase.

Score
Recommendation

It is recommended to document this assumption clearly in the system specifications to mitigate any potential errors eventually arising from decimal inconsistencies in future upgrades.

Remediation Plan

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

7.15 Use of full file imports

// Informational

Description

Throughout the codebase, there are multiple instances where full file imports are used. While this does not affect functionality, it is not considered a best practice.

Score
Recommendation

Use named import syntax instead of importing full files. This restricts what is being imported to just the named items, not everything in the file, for example:

import {MyContract} from "src/MyContract.sol"

Remediation Plan

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

8. Automated Testing

Static Analysis Report

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.

Output

Tagus Labs - slither1
Tagus Labs - slither2
Tagus Labs - slither3
Tagus Labs - slither4Tagus Labs - slither5Tagus Labs - slither6

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

Unit tests and fuzzing

The original repository used the Hardhat environment to develop and test the smart contracts. All tests were executed successfully. Additionally, the project in scope was cloned to a Foundry environment, to allow for additional testing and fuzz testing that covered ~50,000 runs per test. These additional tests were run successfully.

Fuzz testing

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.