Veraswap
Cross-chain Swaps

Examined the on-chain swap contracts combining Uniswap Universal Router, Hyperlane cross-chain messaging, and ERC-7579 accounts.
report id
0622
category
Cross-chain & Bridges
language
Solidity
final report
September 16, 2025
lines of code
346
Content

Download audit report

1

Executive Summary

This document presents the security review performed by Nethermind Security for Veraswap. Veraswap is an innovative project that aims to provide the best cross-chain swapping experience. Users who swap with Veraswap do not have to worry if the token is on their chain, since swapping and bridging are abstracted from the user and made convenient. To achieve that, Veraswap utilizes Uniswap Universal Router commands, Hyperlane cross-chain messaging, and ERC7579 accounts. Veraswap's swapping logic is designed in a modular way, which allows for easy introduction of new features or changing parts of a mechanism to an alternative implementation. Such a design is not only easier to maintain and secure, but also invites contributors who wish to propose improvements to the protocol.

The security review focused on the smart contracts that are the core of the on-chain part of the application. Those contracts are going to be deployed on the chosen blockchains.

The audit was performed using (a) manual analysis of the codebase, (b) automated analysis tools, and (c) creation of test cases.

Remarks about the Veraswap tests

Veraswap supplied a substantial amount of tests covering many scenarios. However, those tests seem to be too generic and do not cover in-depth all important interactions and scenarios. The Veraswap protocol interacts with external contracts and bridges, therefore we strongly advise to create a stronger test suite that would cover most important execution paths and include edge cases.

Audit assessment
Audit TypeSecurity Review
Initial ReportSep 2, 2025
Response from ClientRegular responses during audit engagement
Final ReportSep 16, 2025
Repositoryowlprotocol/veraswap
Commit - veraswapc98da9037f
Commit - universal-router20c847b94e
Final Commit - veraswapd9327ea3aa
Final Commit - universal-routerfb6900abc2
DocumentationMeetings and code comments
Documentation AssessmentMedium
Test Suite AssessmentLow
Total findings
8
Fixed
6
Acknowledged
1
Mitigated
1
Unresolved
0

Findings by severity

  • Critical0
  • High0
  • Medium2
  • Low4
  • Informational2
  • Best Practices0

2

Audited Files

#ContractLoCCommentsRatioBlankTotal
1OwnableSignatureExecutor.sol583662.1%14108
2ERC7579ExecutorRouter.sol1286450.0%20211
3StargateBridgeSweep.sol833744.6%30150
4HypTokenRouterSweep.sol342882.4%971
5PR #449432046.5%1772
Total34618553.5%90621

4

System Overview

The Veraswap protocol abstracts complicated cross-chain interactions to allow users to perform swaps regardless of which chain the token exists on. Veraswap allows users to perform four distinct actions: (1) Swap (2) Bridge (3) Swap and Bridge (4) Bridge and Swap. Action (3) is performed by utilizing the UniversalRouter, which is modified by Veraswap team and contains the additional CALL_TARGET command to interact with the token bridge contracts. The action (4) requires more complex interaction, since it performs a swap action on the remote chain. Veraswap designed the mechanism so that the user doesn't need to know that tokens are bridged. Below, the bridging of tokens and remote swap execution are described in more detail.

Most important interaction for swap + bridge and bridge + swap paths paths
Fig. 2: Most important interaction for swap + bridge and bridge + swap paths paths

4.1 Universal Router's Proposed Addition of CALL_TARGET Command (PR #449)

Veraswap’s PR #449 proposes extending Uniswap’s UniversalRouter with a new command CALL_TARGET with command ID 0x22 that allows the router to invoke arbitrary external contracts at any point during execution, improving interoperability with protocols not covered by the existing model. The command’s inputs are ABI-encoded as (address target, uint256 value, bytes data) and are decoded via a lightweight CalldataCallTargetDecoder.sol contract also introduced in the proposal. Dispatcher.sol is amended so that when it encounters 0x22 it performs a low-level call to target with the provided value and data, while explicitly rejecting target == Permit2 to prevent bypassing the router’s curated Permit2 surfaces. As with all Universal Router commands, bit 7 of the command byte independently controls allow-revert semantics, preserving the UniversalRouter's design behaviour.

4.2 Sweeper Contracts

Veraswap’s two sweeper contracts, HypTokenRouterSweep.sol and StargateBridgeSweep.sol act as post-swap executors. After a Uniswap trade, the sweeper contract utilizes all balance of the output token and initiates a cross-chain transfer to the designated recipient, using either Hyperlane or Stargate to perform the bridge call.

4.3 Bridging and Swapping

The "Bridge and swap" action is not initiated through the UniversalRouter. The EOA account performs two calls during this action. The first one is the transfer of the source token to the destination chain. It is performed by Hyperlane or Stargate standard token bridge. Those tokens are sent to a deterministically calculated address of the remote account. During a second call, the user's EOA account calls the ERC7579ExecutorRouter.callRemote(...), which triggers the cross-chain message mechanism. On the destination chain, the ERC7579ExecutorRouter.handle(...) handles the received message. If it is the first call to this chain, the account factory deploys the ERC7579 account, for which the zerodevapp Kernel account implementation is used. The initData, from the cross-chain message, is used for the account initialization. During the initialization, the OwnableSignatureExecutor executor module is installed. Note that initData can't be tampered with, since the account's address depends on it, so any change would deploy the account at another address. Next the ERC7579ExecutorRouter.handle(...) calls the OwnableSignatureExecutor.executeOnOwnedAccountWithSignature(...) with according calldata signed by the EOA. This calldata during the first call should trigger the ERC7579 account to call ERC7579ExecutorRouter.setAccountOwners(...) and register the EOA as an owner with the corresponding remote router address and origin domain. With this setup, every next cross-chain call can be done without any additional signatures, but just with a direct call to the ERC7579 account from Router through OwnableSignatureExecutor.executeOnOwnedAccount(...). When the bridged tokens arrive at the account, the Executor contract triggers the account to execute the swap. The swap is performed by the UniversalRouter. The user can also perform remote calls on the account from a new chain by just signing calldata and executing it through OwnableSignatureExecutor.executeOnOwnedAccountWithSignature(...) to register a new remote router and origin domain in the local ERC7579ExecutorRouter router.

5

Risk Rating Methodology

The risk rating methodology used by Nethermind Security follows the principles established by the OWASP Foundation. The severity of each finding is determined by two factors: Likelihood and Impact.

Likelihood measures how likely the finding is to be uncovered and exploited by an attacker. This factor will be one of the following values:

  1. High: the issue is trivial to exploit and has no specific conditions that need to be met;
  2. Medium: the issue is moderately complex and may have some conditions that need to be met;
  3. Low: the issue is very complex and requires very specific conditions to be met.

When defining the likelihood of a finding, other factors are also considered. These can include but are not limited to motive, opportunity, exploit accessibility, ease of discovery, and ease of exploit.

Impact is a measure of the damage that may be caused if an attacker exploits the finding. This factor will be one of the following values:

  1. High: the issue can cause significant damage, such as loss of funds or the protocol entering an unrecoverable state;
  2. Medium: the issue can cause moderate damage, such as impacts that only affect a small group of users or only a particular part of the protocol;
  3. Low: the issue can cause little to no damage, such as bugs that are easily recoverable or cause unexpected interactions that cause minor inconveniences.

When defining the impact of a finding, other factors are also considered. These can include but are not limited to data and state integrity, loss of availability, financial loss, and reputation damage. After defining the likelihood and impact of an issue, the severity is determined according to the table below.

Impact / LikelihoodLowMediumHigh
HighMediumHighCritical
MediumLowMediumHigh
LowInfo / Best PracticesLowMedium
UndeterminedUndeterminedUndeterminedUndetermined

To address issues that do not fit a High, Medium or Low severity, Nethermind Security also uses three more finding severities: Informational, Best Practices, and Undetermined.

  1. Informational findings do not pose any risk to the application, but they carry some information that the audit team intends to pass to the client formally;
  2. Best Practice findings are used when some piece of code does not conform with smart contract development best practices;
  3. Undetermined findings are used when we cannot predict the impact or likelihood of the issue.

6

Issues

6.1 Failed bridged message cannot be canceled

FileFix commitTypeStatus
ERC7579ExecutorRouter.solPR #370MediumMitigated

The Hyperlane allows for replaying failed messages by any relayer by calling process(). However, there is no option to cancel the message. A user who uses Veraswap may perform the bridge and swap action, which bridges the tokens to the destination chain and then bridges the calldata, which is used by the ERC7579 account to perform the swap. If the swap fails (e.g., due to slippage), the user may want to bridge tokens back to the origin chain by submitting another cross-chain call. However, the first swap call, which failed, is still present in the Mailbox and can be executed in the future. If the user were to use Veraswap to perform another swap using the same source token, the first failed swap may be executed by anyone who calls Mailbox.process() with the failed message. As a result, the user's funds would be used for the first swap and not the newest swap.

Recommendation

Since Hyperlane does not allow for cancellation of verified messages that failed on execution, solving the described issue requires handling it on the Veraswap side. One possible scenario would involve stopping the use of the account after bridging back the tokens if the swap fails and the user does not want to execute it. Then on the next call, the new account should be deployed. Another solution could be implementing the registry in the ERC7579ExecutorRouter, which would allow the user to invalidate chosen calldata, so that when it is processed in the Mailbox, the ERC7579ExecutorRouter would not perform any call to the executor.

Resolution

Reduced execution deadline to 10 minutes. Messages cannot be cancelled, but expire by themselves once their deadline is passed. PR

6.2 Incorrect refund address

Sweepers do not specify the sender EOA address as the fee surplus or swap leftover as the recipient. In the StargateBridgeSweep, the _refundAddress is specified as the bridge transfer recipient:

function bridgeAllETH(address payable to, BridgeParams calldata bridgeParams) external {
...
pool.sendToken{value: balance}({_sendParam: sendParam, _fee: fee, _refundAddress: bridgeParams.recipient});

// Refund user with any excess native tokens
uint256 leftoverBalance = address(this).balance;
if (leftoverBalance > 0) { payable(bridgeParams.recipient).transfer(leftoverBalance);
}
}

The bridgeParams.recipient can be any address to which the user wants to bridge funds, which does not have to be the user's controlled EOA, but the fee surplus and the ETH leftover will be sent to this address.

In the HypTokenRouterSweep, there are two functions, where only one allows for defining the EOA sender in the hookMetadata as the fee surplus recipient:

// @audit: this function would return the fee surplus to the sweeper
function transferRemote(address router, uint32 destination, bytes32 recipient, uint256 bridgePayment)
// @audit: this function allows for defining the EOA as the refund recipient in the hookMetadata
function transferRemote(
address router,
uint32 destination,
bytes32 recipient,
uint256 bridgePayment,
bytes calldata hookMetadata,
address hook
)

Recommendation

Consider always defining the EOA as the fee surplus and the ETH leftover as a recipient. This can be done by setting the user's EOA as the _refundAddress in the StargateBridgeSweep and defining the EOA address in the hookMetadata with the corresponding hook in the HypTokenRouterSweep. Moreover, ensure that the calls made directly from the EOA account during Bridge & Swap also define the EOA as the refund address.

Resolution

Resolved for Stargate, acknowledged for Hyperlane, although not launch-critical, as refund is limited to excess fees

HypTokenRouterSweep.sol: As mentioned, refundAddress can be encoded in the hookMetadata. The Veraswap frontend does not do this yet, but this is a marginal issue considering the refund amount. Can be fixed client-side. No Solidity changes necessary.

StargateBridgeSweep.sol: Added a refund address parameter bridgeAllETH and bridgeAllToken, commit.

6.3 Lack of target restriction in UniversalRouter

FileFix commitTypeStatus
Dispatcher.sol—LowAcknowledged

The CALL_TARGET command allows for arbitrary calls to any address, excluding the PERMIT2. This allows malicious users to use the UniversalRouter contract to perform transactions that are against the law. This may include interactions with blacklisted protocols, sending funds to criminals, or using the UniversalRouter as part of the exploit. Since the UniversalRouter is a core part of the Veraswap protocol, such transactions may involve legal or reputation issues. Note that no such scenario has been registered yet; therefore, the probability of such an event is considered minor.

Recommendation

Consider introducing a mapping of allowed target addresses to minimize the legal and reputation risk.

Resolution

This is an accepted risk and is not much different from having the UniversalRouter interact with a malicious Uniswap pool. The entire purpose of the CALL_TARGET command is to enable open modularity, and as such, any allowlist would defeat its purpose. The Veraswap UI only uses the CALL_TARGET command to interact with our audited contracts (eg, HyperlaneSweeper, StargateSweeper).

The changes to the UniversalRouter are released under an open-source license and deployed on-chain as public good infrastructure on which we have no control over post-deployment. Developers are responsible for using the CALL_TARGET command responsibly in their projects.

6.4 transfer(...) used for native currency transfer

FileFix commitTypeStatus
StargateBridgeSweep.solPR #376LowFixed

The StargateBridgeSweep contract in the bridgeAllETH(...) transfers any leftover ETH to the specified recipient. However, the ETH transfer is performed with the .transfer(...) function. This function limits the passed gas to 2300 weis. While it was designed to protect against reentrancy, it is not a recommended way of transferring ETH, since the receiver address may need more gas to handle such a transfer (e.g., wallets) or the gas cost for specific opcodes may be changed in the future. Please refer to this article for more in-depth information. Note that while .transfer(...) was designed to protect against re-entrancy, those protections should be applied by the CEI pattern or the non-reentrant modifiers.

Recommendation

Consider using .call(...) for native currency transfers.

Resolution

Changed to using CurrencyLibrary in the base Sweep.sol contract that implements shared behaviour between StargateBridgeSweep.sol and HyperlaneSweep.sol. The refund logic is now implemented in the internal _sweep() function of Sweep.sol. Native token refund logic has also been added to the bridgeAllToken function if the user sends too many native tokens to the sweeper.

Commit (diff and reference to the _sweep function)

6.5 Inconsistent application of msg.value in OwnableSignatureExecutor

FileFix commitTypeStatus
OwnableSignatureExecutorPR #377LowFixed

The executeBatchOnOwnedAccountWithSignature (...) function in the OwnableSignatureExecutor contract executes the batch of transactions on the owned account using msg.value as:

function executeBatchOnOwnedAccountWithSignature(
SignatureExecutionLib.SignatureExecution calldata signatureExecution,
bytes calldata signature
) external payable {
// ...
// execute the batch of transactions on the owned account
IERC7579Account(signatureExecution.account).executeFromExecutor{value: msg.value}( //@audit signatureExecution value, why msg.value directly.
ModeLib.encodeSimpleBatch(),
signatureExecution.callData
);
}

While executeOnOwnedAccountWithSignature (...) function is implemented as:

function executeOnOwnedAccountWithSignature(
SignatureExecutionLib.SignatureExecution calldata signatureExecution,
bytes calldata signature
) external payable {
// ....
IERC7579Account(signatureExecution.account).executeFromExecutor{value: signatureExecution.value}(
ModeLib.encodeSimpleSingle(),
signatureExecution.callData
);
}

The executeOnOwnedAccountWithSignature (...) function uses the signed msg.value as value: signatureExecution.value while the executeBatchOnOwnedAccountWithSignature (...) function uses the caller's passed msg.value as value: msg.value.

This inconsistency leads to a varied amount of native token, msg.value passed to the user's account. As a result, if the user wanted a specific amount of native token to be added to their account, this is not possible.

Additionally, given that the executeBatchOnOwnedAccountWithSignature (...) function is an external function, anyone can call this function directly with the right signatureExecution and signature data, but with an altered msg.value, which is different from the user-intended signed msg.value. This will successfully execute the target's calls without the intended msg.value, but it will consume the account's nonce, effectively taking away the ability of a user to forward a given native token amount to their account.

Recommendation

Consider using the signatureExecution.value as is done in executeOnOwnedAccountWithSignature(...) function.

Resolution

We have implemented the fix and added comments regarding the purpose of the signatureExecution.value to clarify that it does not impact the value of the calls themselves but rather serves as a constraint on having a deposit to the smart account. The signatureExecution.value will be 0 for most use cases. Commit

6.6 Lack of protection during interaction with Position Managers

FileFix commitTypeStatus
Dispatcher.solPR #449LowFixed

The CALL_TARGET command allows for arbitrary calls to any address. This includes managing positions in Uniswap V3 and V4. The commands that manage positions in Universal Router apply restrictions in those scenarios:

function dispatch(bytes1 commandType, bytes calldata inputs) internal returns (bool success, bytes memory output) {
uint256 command = uint8(commandType & Commands.COMMAND_TYPE_MASK);
success = true;
// 0x00 <= command < 0x21
if (command < Commands.EXECUTE_SUB_PLAN) {
// 0x00 <= command < 0x10
...
} else {
// 0x10 <= command < 0x21
if (command == Commands.V4_SWAP) {
// pass the calldata provided to V4SwapRouter._executeActions (defined in BaseActionsRouter)
_executeActions(inputs);
// This contract MUST be approved to spend the token since its going to be doing the call on the position manager
} else if (command == Commands.V3_POSITION_MANAGER_PERMIT) {
_checkV3PermitCall(inputs);
(success, output) = address(V3_POSITION_MANAGER).call(inputs);
} else if (command == Commands.V3_POSITION_MANAGER_CALL) {
//@audit: check the caller and restrict actions
_checkV3PositionManagerCall(inputs, msgSender());
(success, output) = address(V3_POSITION_MANAGER).call(inputs);
} else if (command == Commands.V4_INITIALIZE_POOL) {
PoolKey calldata poolKey;
uint160 sqrtPriceX96;
assembly {
poolKey := inputs.offset
sqrtPriceX96 := calldataload(add(inputs.offset, 0xa0))
}
(success, output) =
address(poolManager).call(abi.encodeCall(IPoolManager.initialize, (poolKey, sqrtPriceX96)));
} else if (command == Commands.V4_POSITION_MANAGER_CALL) {
// should only call modifyLiquidities() to mint
//@audit: restrict actions
_checkV4PositionManagerCall(inputs);
(success, output) = address(V4_POSITION_MANAGER).call{value: address(this).balance}(inputs);
} else {
// placeholder area for commands 0x15-0x20
revert InvalidCommandType(command);
}
}
...
}

Those checks protect the users who were tricked into approving their position NFT to the Universal Router, which would allow malicious actors to perform token burns, decrease of liquidity, or collection of rewards. The CALL_TARGET does not perform those checks, leaving this scenario to be potentially exploited.

Recommendation

Consider excluding addresses for which the Universal Router has additional checks when using the CALL_TARGET command.

Resolution

Fully excluded all hard-coded addresses that have dedicated commands for interactions. The attack surface is now significantly reduced. CALL_TARGET can now only be used with addresses that are "out of scope" of the original Universal Router. We've also added a detailed comment explaining where the immutable constants are inherited from.

// Call target cannot be one of the following addresses to reduce the attack surface and enforce proper use of commands
// when interacting with protocol specific addresses
// - address(self): to enforce use of EXECUTE_SUB_PLAN command for self re-entrancy
// - PaymentsImmutables.sol: to avoid arbitrary token transfers
// - UniswapImmutables.sol: to have clear protocol interactions with v2,v3 factories
// - MigratorImmutables.sol: to have clear protocol interactions with v3,v4 position managers
// - ImmutableState.sol (v4-periphery): to have clear protocol interactions with v4 pool actions
if (
target == address(self) || target == address(WETH9) || target == address(PERMIT2)
|| target == address(UNISWAP_V2_FACTORY) || target == address(UNISWAP_V3_FACTORY)
|| target == address(V3_POSITION_MANAGER) || target == address(V4_POSITION_MANAGER)
|| target == address(poolManager)
) {
revert CallTargetInvalid(target);
}

6.7 Potential reentrancy during interaction with the bridge

The Veraswap protocol interacts with bridges to transfer tokens and to execute swaps on remote chains. During interaction with the bridge, the fee is sent in native currency. If the fee sent is too large, then the bridge returns the surplus to the specified recipient. Such a recipient can be a contract that can execute any code during a callback with native currency.

Recommendation

Since the bridge interactions are not made mid-execution, we didn't identify any immediate risk. However, this behaviour should be documented and shared with the users. Additionally, the interactions with bridges should be performed as the last action of the Veraswap multicalls.

Resolution

In our current architecture, reentrancy from bridge interactions poses no issue, though this could be a concern in the future. The UniversalRouter is unaffected as it already has reentrancy protection. The sweeper contracts are also unaffected, assuming only one token at a time is sent to the sweeper. This is the case in Veraswap, where users are only making single token pair swaps.

In a more complex scenario, the sweeper contracts could be affected. For example, a user might send the sweeper two tokens to bridge, and then call transferRemote on each of them. In this scenario, a transferRemote call for the first token, followed by a native token refund from the bridging protocol to a malicious recipient, could cause a reentrancy calling transferRemote on the second token and stealing the tokens the user had meant to bridge to themselves.

While not affecting Veraswap's current implementation, we fixed the reentrancy risk to the sweeper contracts by adding a TSTORE-based reentrancy lock similar to UniversalRouter to future-proof the sweeper modules. This was fixed below, where all sweepers now inherit from Sweep.sol, which inherits from Lock.sol that implements a TSTORE-based lock. All relevant functions are then protected using the isNotLocked modifier.

6.8 Transfers of funds are not checked to be successful

FileFix commitTypeStatus
Dispatcher.solPR #449InfoFixed

The UniversalRoutercontract's dispatcher logic handles a CALL_TARGET command, which is designed to execute arbitrary calls to external contracts. This is achieved using a low-level call(...). Low-level calls like .call do not propagate reverts from the callee. Instead, they return a boolean value indicating whether the call succeeded or failed.

// ...
function dispatch(...) internal {
// ...
} else if (command == Commands.CALL_TARGET) {
(address target, uint256 value, bytes calldata data) = CalldataCallTargetDecoder.decodeCallTarget(
inputs
);
if (target == address(PERMIT2)) revert CallTargetPermit2();

// @audit The `success` boolean is not checked.
(success, output) = payable(target).call{value: value}(data);
// ...
}
// ...
}

The UniversalRouter command structure includes an allowRevert flag to control whether a command's failure reverts the entire transaction. However, it allows for failure during the tokens or native currency transfers. This differs from other commands, which utilize the Payments contract, which performs funds transfers with safeTransferETH(...) and safeTransfer(...) that require always successful transfers.

Recommendation

Consider properly documenting described behaviour.

Resolution

Added comments detailing the CALL_TARGET command's behaviour, its similarity with other commands that return success, and its difference from commands that always revert, such as those that handle token transfers.

// Call may fail without a revert if Commands.FLAG_ALLOW_REVERT is set
// This behavior is similar to other commands that use .call (eg. V3_POSITION_MANAGER_CALL, V4_POSITION_MANAGER_CALL)
// This behavior is different other commands that transfer ETH or tokens (eg. TRANSFER)
(success, output) = payable(target).call{value: value}(data);

7

Documentation Evaluation

Software documentation refers to the written or visual information that describes the functionality, architecture, design, and implementation of software. It provides a comprehensive overview of the software system and helps users, developers, and stakeholders understand how the software works, how to use it, and how to maintain it. Software documentation can take different forms, such as user manuals, system manuals, technical specifications, requirements documents, design documents, and code comments. Software documentation is critical in software development, enabling effective communication between developers, testers, users, and other stakeholders. It helps to ensure that everyone involved in the development process has a shared understanding of the software system and its functionality. Moreover, software documentation can improve software maintenance by providing a clear and complete understanding of the software system, making it easier for developers to maintain, modify, and update the software over time. Smart contracts can use various types of software documentation. Some of the most common types include:

  • Technical whitepaper: A technical whitepaper is a comprehensive document describing the smart contract’s design and technical details. It includes information about the purpose of the contract, its architecture, its components, and how they interact with each other;
  • User manual: A user manual is a document that provides information about how to use the smart contract. It includes step-by-step instructions on how to perform various tasks and explains the different features and functionalities of the contract;
  • Code documentation: Code documentation is a document that provides details about the code of the smart contract. It includes information about the functions, variables, and classes used in the code, as well as explanations of how they work;
  • API documentation: API documentation is a document that provides information about the API (Application Programming Interface) of the smart contract. It includes details about the methods, parameters, and responses that can be used to interact with the contract;
  • Testing documentation: Testing documentation is a document that provides information about how the smart contract was tested. It includes details about the test cases that were used, the results of the tests, and any issues that were identified during testing;
  • Audit documentation: Audit documentation includes reports, notes, and other materials related to the security audit of the smart contract. This type of documentation is critical in ensuring that the smart contract is secure and free from vulnerabilities.

These types of documentation are essential for smart contract development and maintenance. They help ensure that the contract is properly designed, implemented, and tested, and they provide a reference for developers who need to modify or maintain the contract in the future.

8

Remarks & Test Suite

Veraswap supplied comprehensive documentation prior to the audit, including a detailed Notion document and GitHub repository. The team also conducted a thorough code walkthrough during the kick-off meeting. Furthermore, Veraswap promptly addressed all questions and concerns raised by the Nethermind Security team, offering valuable insights and a clear understanding of the project's technical details

Test suite
93Tests passed0Failed

The complete test output is in the full report.

As auditors, we recommend that any changes or updates made to the audited codebase undergo a re-audit or security review to address potential vulnerabilities or risks introduced by the modifications. By conducting a re-audit or security review of the modified codebase, you can significantly enhance the overall security of your system and reduce the likelihood of exploitation. However, we do not possess the authority or right to impose obligations or restrictions on our clients regarding codebase updates, modifications, or subsequent audits. Accordingly, the decision to seek a re-audit or security review lies solely with you.

This report is based on the scope of materials and documentation provided by you to Nethermind in order that Nethermind could conduct the security review outlined in 1. Executive Summary and 2. Audited Files. The results set out in this report may not be complete nor inclusive of all vulnerabilities. Nethermind has provided the review and this report on an as-is, where-is, and as-available basis. You agree that your access and/or use, including but not limited to any associated services, products, protocols, platforms, content, and materials, will be at your sole risk. Blockchain technology remains under development and is subject to unknown risks and flaws. The review does not extend to the compiler layer, or any other areas beyond the programming language, or other programming aspects that could present security risks. This report does not indicate the endorsement of any particular project or team, nor guarantee its security. No third party should rely on this report in any way, including for the purpose of making any decisions to buy or sell a product, service or any other asset. To the fullest extent permitted by law, Nethermind disclaims any liability in connection with this report, its content, and any related services and products and your use thereof, including, without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement. Nethermind does not warrant, endorse, guarantee, or assume responsibility for any product or service advertised or offered by a third party through the product, any open source or third-party software, code, libraries, materials, or information linked to, called by, referenced by or accessible through the report, its content, and the related services and products, any hyperlinked websites, any websites or mobile applications appearing on any advertising, and Nethermind will not be a party to or in any way be responsible for monitoring any transaction between you and any third-party providers of products or services. As with the purchase or use of a product or service through any medium or in any environment, you should use your best judgment and exercise caution where appropriate. FOR AVOIDANCE OF DOUBT, THE REPORT, ITS CONTENT, ACCESS, AND/OR USAGE THEREOF, INCLUDING ANY ASSOCIATED SERVICES OR MATERIALS, SHALL NOT BE CONSIDERED OR RELIED UPON AS ANY FORM OF FINANCIAL, INVESTMENT, TAX, LEGAL, REGULATORY, OR OTHER ADVICE.