Smartlink

12 juillet, 2023
logo smart link
Audit infos

This security audit was prepared by euraNov. We cannot guarantee the absolute absence of issues. The audit's scope is limited to the Solidity code and may not cover other project components or future changes. It also acknowledges that the auditor's liability is limited to the professional services provided during the audit. euraNov cannot be responsible for errors, omissions, or the direct or indirect consequences, damages, losses, or fraud arising from the use of theses smart contracts.


Summary

Type : Escrow
Languages : Solidity
Total Issues : 25 (22 resolved)
Critical Severity : 0 (0 resolved)
High Severity: 3 (3 resolved)
Medium Severity: 1 (1 resolved)
Low Severity : 5 (4 resolved)
Notes & Additional Information : 16 (14 resolved)


Scope

We audited the Escrow/smart-contract-solidity repository at the 7eddbd35 commit.
In scope were the following contracts for the :

- contracts/Escrow.sol

- contracts/Factory.sol
Update @15 juin 2023 : Update of this audit report after incorporating the changes made by the development team (last commit 8cba5854).


System Overview

Firstly, the Factory smart contract is a simple contract that aims to facilitate and secure the deployment of Escrow smart contracts.

The escrow system is a smart contract designed to facilitate secure transactions between two parties by temporarily holding funds until predefined conditions are met. This escrow system is particularly useful for transactions involving digital assets, services, or high-value goods, providing a trustless mechanism to ensure all parties fulfill their obligations. The Escrow contract is an extension of a simple Escrow smart contract, introducing additional features and improvements over the basic escrow functionality.

The primary difference lies in the inclusion of user-defined release conditions. The escrow system expands upon the traditional releaseFunds function by incorporating a more specific condition-checking mechanism. This mechanism allows users to define custom conditions in the form of a timeline such as withdrawal, delivery, inspection, and extension periods. The release of funds is only authorized when the defined conditions are fulfilled, providing enhanced security and flexibility for users.

Another distinction is the implementation of a transaction tracking system within the contract. The Escrow contract employs a comprehensive transaction status tracking system that monitors the progress of each transaction through different statuses. This system replaces the basic released mapping in a basic Escrow contract. By tracking transaction statuses the escrow system enables additional functionality, such as multi-stage transactions and conditional fund releases. Admin can set predefined milestones for the release of funds, ensuring each stage of a transaction is completed as agreed upon.

Another important and notable aspect is the implementation of an arbitration system that allows an external person to arbitrate in the event of a transaction dispute between the two parties. This arbitrator is pre-defined in the contract (whitelisted) and operates in exchange of arbitration fees.

The main covered aspects are :
The buyer can initiate an escrow transaction by calling the addOrder or addOrderByToken methods on the smart contract.

The seller can cancel the transaction at any time (funds are automatically returned to the buyer) by calling the cancelOrder method.

During the withdrawal period, the buyer can cancel the transaction by calling the cancelOrder method.

During the inspection period:

     a. The buyer can confirm the delivery by calling the completeOrder method.
      b. The seller and buyer can request an extension of the inspection period by calling the extendInspectionRequest method.
      c. The buyer and seller can request a settlement offer by calling the settlementRequest method.
      d. The buyer and seller can initiate an arbitration process by calling the addDispute or addDisputeByToken methods.

After the inspection period, the seller can claim the funds for the transaction by calling the claim method.

A final claim can be made after one year by the admin of the Factory by calling the superClaim method.

In summary, this escrow system offers a secure, flexible, and customizable escrow solution that caters to a diverse range of transaction types and user requirements. It introduces user-defined release conditions and advanced transaction tracking, ensuring a more versatile escrow experience for all parties involved.


High Severity Issues

Escrow.sol Contract Cannot Be Paused

Smart contracts can contain vulnerabilities, bugs, or under specific circumstances such as technical issues, it’s essential to be able to temporarily stop the execution of the contract to prevent any further exploitation. The pause function allows developers to react quickly to such situations and protect the involved users and assets. This promotes better risk management and enhances user experience. We strongly recommend implementing a pause function.

✅ Corrected : 3011039f



Uninitialized stat

Proper initialization of state variables in a Solidity smart contract is essential to prevent undesired behaviors, ensure security, facilitate interoperability, and improve code readability. This contributes to the contract's robustness, security, and maintainability. For these reasons, it is crucial to correctly initialize the variables Escrow.pause #L25 and Factory.pause #L9 .

✅ Corrected : 1446763f



Variable Name Error

If we follow the logic, it appears that in the Escrow.sol contract, in the superClaim function, the transfer should be made to the address of the super_admin rather than the admin's address #L507.

✅ Corrected : 1446763f



Medium Severity Issues


Arrays Lenght

The changeConfig function allows the admin to update token statuses and the fees_paid_byvalue. However, there is no validation to ensure that the _tokens and _tokenStatus arrays have the same length. If they have different lengths, this can lead to unexpected behavior. You should add a check to ensure that the lengths are equal.

✅ Corrected : 1446763f



Low Severity Issues


Shadowing Local Variable

In the changeConfig function, the variable _status#L202 has the same name as a variable present in the ReentrancyGuard.sol contract. In Solidity, it is possible to use the same variable name in different scopes; however, this can potentially lead to undesirable effects. To improve readability and minimize errors, we recommend renaming the variable to _tokensStatus.

✅ Corrected : fd2f4254


Reentrancy

In the addOrderByToken function, even though a reentrancy attack has no interest in this function, it is still preferable to follow the checks-effects-interactions pattern to maintain consistency in the code and avoid any potential attack vectors.

✅ After discussing with the development team, this issue will not be taken into account and is therefore considered resolved.


Missing Zero Check Address

By checking addresses are not equal to address 0, we avoid transfer errors to an invalid address and prevent the loss of funds. Furthermore, an invalid address such as address 0 can be exploited by attackers to manipulate the smart contract's functionality. It’s important to verify that addresses are not equal to address 0 in order to ensure data validity, security, and enhance the contract's robustness.

Escrow.constructor  :
admin = _admin #L92
affiliation_address = _affiliation_address #L101
fee_collector = _fee_collector #L94
arbitrator_address = _arbitrator_address #L93

Escrow.changeAffiliate function :
affiliation_address = _address #L191

Escrow.changeFeeCollector function :
fee_collector = _address #L195

Escrow.changeSuperAdmin function :
super_admin = _address #L183

Escrow.changeArbitrator function :
arbitrator_address = _address #L187

Factory.constructor :
arbitrator_address = _arbitrator_address #L21
fee_collector = _fee_collector #L22

Factory.changeArbitrator :
arbitrator_address = _address #L63

Factory.changeAdmin :
admin = _address #L40

Factory.changeFeeCollector :
fee_collector = _address #L43

❌ After discussing with the development team, adding verification in correlation with the other modifications would have resulted in excessive contract size. In the interest of optimization, this issue is therefore not resolved but also not a security issue here.


Variable Initialization

The initialization of the counter variable before the start of the loop ensures that its value is correct from the beginning, thus avoiding unexpected behaviors or incorrect calculations. As best practice, we recommend to properly initializing the i variable in the Escrow.addToken function #L158.

✅  After discussing with the development team, due to the fact that a uint type variable is initialized by default to the value of 0, this issue has been decided to be marked as solved.


Low Level Calls

When performing a transfer or any other interaction with an external  smart contract, it is preferable to use an IERC20 interface to interact with it for several reasons. Using an IERC20 interface ensures clarity and readability of the code by concisely declaring the specific functions and events of an ERC20 contract. Additionally, it provides enhanced security and reusability by utilizing standardized functions proven by the community. The use of an IERC20 interface is recommended to improves code quality, security, maintainability, and interoperability of the smart contract.

Factory.recoverTokens #L68
Escrow.addOrderByToken #L223
Escrow.cancelOrder #L285
Escrow.completeOrder #L313
Escrow.completeOrder #L316
Escrow.completeOrder #L319
Escrow.acceptSettlementRequest #L370
Escrow.acceptSettlementRequest #L373
Escrow.acceptSettlementRequest #L376
Escrow.acceptSettlementRequest #L379
Escrow.addDisputeByToken #L438
Escrow.addDisputeByToken #L440
Escrow.claim #L478
Escrow.claim #L481
Escrow.claim #L484
Escrow.superClaim #L509


Settlement Request Percentage Check

The settlementRequest function allows the buyer or the admin to request a settlement for the order, but it does not have a check to ensure that the _percentage argument is within a valid range.

✅ Corrected : 1446763f



Notes & Additional Information

Checks-effects-interactions pattern

In these two functions addDisputeByToken and addOrderByToken, the non-reentrancy modifier has been added, however the checks-effects-interactions pattern is not followed. Even though the modifier serves as a security measure, it is still preferable to adhere to this pattern to minimize the potential surface for a reentrancy attack.

✅  After discussing with the development team, the non-reentrancy modifier is deemed sufficient. Therefore, this issue is considered solved.


Naming Convention

Throughout the Escrow.sol smart contract, you use different naming conventions, such as snake_case and camelCase (for example: extensionRequests #L60, order_ids #L57). For better code readability and maintainability, its preferable to use a single and consistent naming convention across all smart contracts.

✅  After discussing with the development team, this issue is considered resolved.


Immutable States

Immutable variables cannot be modified after their initialization. This ensures their integrity and prevents potential errors from manipulation or accidental modification. Additionally, immutable variables are optimized by the compiler, which can improve performance and reduce gas costs during contract deployment and execution. We recommend declaring the following variables as immutable:

Escrow.delivery_period #L18
Escrow.extension_period #L20
Escrow.request_reply_period #L21
Escrow.admin #L15
Escrow.factory_contract_address #L16
Escrow.withdrawal_period #L17
Escrow.inspection_period #L19

✅ Corrected : 1446763f


Error Messages

require statements use short error codes, more descriptive error messages can improve readability and debugging.

✅ After discussing with the development team, with a focus on optimizing contract size, the team has chosen to keep short error codes. A comprehensive documentation will be provided for users. This issue is therefore considered resolved.


Upgradability

The Escrow.sol contract does not have any built-in upgradability features. If you anticipate needing to make changes to the contract's logic after deployment, consider implementing a proxy pattern or using a more modular design that allows individual components to be upgraded.

❌ After discussing with the project team, the topic of contract update was not included in this version. Therefore, this issue is not considered resolved.


Smart Contract Documentation

The Escrow.sol & Factory.sol smart contracts does not provide any documentation. Ensure that the smart contract provide clear and comprehensive documentation to guide users and developers on how to interact with the contract.

✅ Corrected : The development team has chosen to place the documentation outside the smart contracts.


Pragma Exact Version

Contracts should be deployed with the same compiler version and flags that they have been tested the most with. Locking the pragma helps ensure that contracts do not accidentally get deployed using, for example, the latest compiler which may have higher risks of undiscovered bugs. Replace pragma solidity ^0.8.17; by pragma solidity 0.8.17; Escrow.sol #L2/Factory.sol #L2.

✅ Corrected : 1446763f


Function To Modifier

You can replace the following functions with modifier to ensure consistency, improved readability, and enhanced maintainability of the code :

Escrow.validStatus #L128
Escrow.validOrder #L139
Escrow.validSettlement #L143

🟠 Partially corrected : 8b2dc676
For optimization purposes, the functions Escrow.validOrder and Escrow.validSettlement have been removed. The function Escrow.validStatus has not been modified.


Event Not Emitted

Events provide transparency and traceability by recording important actions on the blockchain. They allow users, third-party applications, and contracts to monitor and react to changes in the contract's state.We recommend adding the emission of an event for each of the following functions :

Escrow.changeSuperAdmin #L182
Escrow.changeArbitrator #L186
Escrow.changeAffiliate #L190
Escrow.changeFeeCollector #L194
Escrow.changeSuperAdminClaimPeriod #L198
Factory.changeAdmin #L39
Factory.changeFeeCollector #L42
Factory.changeArbitrator #L62

✅ After discussing with the project team, emitting events for these functions was deemed unnecessary. Therefore, this issue is considered resolved.


Duplicated Tokens Data

The token information is stored as a copy during the deployment of an Escrow.sol contract :

The status can be modified independently of the Factory using the changeConfig function, which is accessible only to the Escrow contract administrator. If desired, in the case where a token is blacklisted by modifying its status in the Factory, this status change will not be automatically reflected in the already deployed Escrow contracts.

Once the token fees have been initialized at instantiation, it is not possible to update them from the Escrow. Only the Factory can update the fees, and the new configuration will be used only for deploying future Escrow contracts.

Adding a new token is possible through the changeConfig function, which verifies in the Factory that the token's status is valid. This means that for each token added to the Factory, as long as the administrators of the Escrow contracts have not called the changeConfig function, the token will not be available in their Escrow contract.

✅ After discussing with the project team as well as the development team, the chosen behavior is intentional. Therefore, this issue is considered resolved.


Unused/Missing Statuses

The Escrow.Status.SR (SETTLEMENT_REQUEST) status is not defined in the order statuses when calling the Escrow.settlementRequest function.

The Escrow.Status.NA status is not used #L63.

✅ Corrected : 94582fb6


Configuration Update

The configuration functions in the Escrow contract (only the Factory admin), must be called on each deployed Escrow contract in case of modification : Escrow.changeSuperAdmin, Escrow.changeArbitrator, Escrow.changeAffiliate, Escrow.changeFeeCollector, Escrow.changeSuperAdminClaimPeriod.

The same goes for the Escrow.superClaim function, but it is less sensitive as it is less likely to be called.

✅ After discussing with the project team as well as the development team, the chosen behavior is intentional. Therefore, this issue is considered resolved.


Constructor Arguments

The times #L90 parameter is not clearly defined, and index 5 of this parameter is used for fees_paid_by, which does not correspond to a time period. One solution could be to separate the fees_paid_by parameters from the time periods or rename the times parameter to options to avoid any confusion. This would make the code clearer and easier to understand for developers and users of the escrow contract.

✅ Corrected : 1446763f


Fallback  & Receive Functions

The Factory.sol & Escrow.sol contract don't require to receive standalone Ether payments or perform any action when it receives Ether, you don't need to explicitly include the receive function.

On the other hand, the fallback function i used to handle function calls that didn't match any other defined function in the contract. It is considered good practice to define a fallback function that reverts in case of function calls that didn't match any other defined function in the contract. This ensures that any unexpected or invalid function calls to your contract are handled properly.

✅ It has been chosen to keep the receive function so that the super admin can manually perform the reverse transfer in case of ETH transfer by mistake on the Escrow.sol and Factory.sol contracts.

Corrected : 305aeb41


Test Execution Failures

The following tests failed during our execution :

Factory.DeployEscrow #L197 : “Should deploy escrow with new address”
Factory.DeployEscrow #L209 : “Should deploy escrow having two token”

✅ Corrected : 8b2dc676


Contract size & optimisation

L’équipe de développement à effectué un nombre notable de modification dans le but d’optimiser la taille du smart contract.

✅ Corrected : 4ef1845c



Conclusion


In conclusion, the Factory.sol contract doesn’t contain any major issues, only a few minor improvements could be made.

On the other hand, the Escorw.sol contract seems to have three high-severity issues. These issues have been processed. Besides this, the contract is using an up-to-date and appropriate version of the Solidity compiler which includes many safety features.

Visibility specifiers are well defined in all functions which is a good practice and can reduce the chance of unintentional behavior. The contract uses modifiers correctly to handle access control and checks for conditions before execution of function bodies. This helps in reducing code repetition and improving readability.

Revert statements with error messages are used in the contract which is a good practice for debugging and understanding why a transaction might have failed. But it is necessary to provide more explicit error messages and/or comprehensive documentation to clearly and easily identify the errors returned by the smart contract.

Given these insights, measures have been taken to prevent any potential reentrancy attack. The "Checks-Effects-Interactions" pattern has been mostly adhered to.

Additionally, to ensure the logic of the contract works as intended, further testing should be conducted, including unit testing, integration testing, and stress testing.

Due to the weight of the Escrow.sol smart contract code, some improvements and/or optimizations regarding the code and gas consumption are currently not possible. The development team has already implemented some optimizations; however, it is important to consider this aspect in the context of future improvement and optimization efforts to ensure the best user experience.



euraNov Confidential - 15 juin 2023