LiquiFi

QuantstampFiles named in the report

Quantstamp reviewed LiquiFi on 27 Jan 2022: 21 findings, 0 critical; 18 marked fixed, 1 acknowledged, 2 open.

No open critical findings
Open the reportOpen LiquiFi’s security record

Report hosted on certificate.quantstamp.com.

Trustblock does not review code.

Findings21

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High002
Medium011
Low2015
High`LinearModule.createPlan` Missing Input ValidationFixed

The function accepts a parameter called `_totalSupply` but does not utilize it properly. It neither checks whether `_cadenceSeconds * _cadenceAmount` is less than the `_totalSupply`, nor whether there are rounding issues that will lead to lost tokens. In case the company owner made a mistake with the inputs, the tokens might vest a lot faster than intended or require manual intervention of the contract owner to retrieve the locked tokens.

HighVesting Cannot Be CancelledFixed

The contract does not have any functionality to allow a company to stop vesting a former employee. There are two kinds of employees, `activeEmployees` and `inactiveEmployees`. The contract keeps track of them but never utilizes this information in regard to enabling/disabling of vesting.

MediumUse of Unsafe ERC20 InterfaceFixed

Functions `Escrow.deposit()` and `Escrow.withdraw()` are using the _unsafe_ ERC20 interface functions of OpenZeppelin (`transferFrom()` and `transfer()`). Since [some ERC20 tokens do not return a value for these functions](https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca) these calls might revert.

Low`createPlan` Allows Overwriting PlansFixed

The `createPlan` function in the modules contracts does not check whether a plan with the provided ID already exists, allowing the owner to overwrite any existing plan.

Low`EmployeeRegistry.isOwner()` Mishandling `address(0)`Fixed

According to the comments, the function `EmployeeRegistry.isOwner()` should return `true` only if the provided address parameter is the owner of of the provided company id parameter and `false` otherwise. However, as the `exists` return value of `EnumerableMap.tryGet(companyOwners, _companyId)` is not checked, providing `address(0)` to any company id would lead to returning `owner == _employee` equal to `true` and therefore violating the comments assumption.

Low`EmployeeRegistry.transferOwner` Might Lead to Unexpected StateFixed

There are two issues: 1. The function `transferOwner` does not check if `_newOwner` is an employee of the `_companyId`. The `_newOwner` could be an inactive employee, or even not an employee of that company. This would violate the assumption that a company owner should be an active employee of that company. 1. The function `transferOwner` does not check if `_newOwner` is not equal to `0x0`. This could lead to a disastrous result: A company has no owner to modify its data.

Low`Escrow.deposit()` and `Escrow.withdraw()` Prone to ReentrancyFixed

Although `Escrow.sol` inherits from `ReentrancyGuardUpgradeable`, it does not make use of the `nonReentrant` modifier. Further, functions `deposit()` and `withdraw()` do not comply with the ["Checks-Effects-Interactions" pattern](https://docs.soliditylang.org/en/v0.8.11/security-considerations.html#use-the-checks-effects-interactions-pattern), by performing state variable changes **after** external function calls and not before (`escrowBalance[][]` being changed after calling `transferFrom()`, `balanceOf()` and `transfer()`). As these lower level functions are only callable by `owner` or `vestingControllerAddress` the potential impact is limited.

LowFunctions Should Not be PayableFixed

The functions `createVestPlan` and `vest` are declared `payable`, allowing ether to be sent along the function calls. Since there is no function to retrieve the ether, it will lead to locked funds.

LowGas For Loop ConcernsFixed

When smart contracts are deployed or their associated functions are invoked, the execution of these operations always consumes a certain quantity of gas, according to the amount of computation required to accomplish them. Modifying an unknown-size array that grows in size over time can result in a Denial-of-Service attack. Simply by having an excessively huge array, users can exceed the gas limit, therefore preventing the transaction from ever succeeding. For the loop on `L242`, the code does not restrict the number of vest plans that an employee in a company can have. This function might run out of gas and cannot be recovered from that state when the employee has too many vest plans. This could happen in case the company owner wants to deny an employee from accessing their vested tokens by adding many low-value vest plans.

LowIncorrect Accounting for `EmployeeRegistry.employeeToCompanyIds`Fixed

The function `EmployeeRegistry.addEmployee()` does not check the return value of the call `EnumerableSet.add(companyIdToEmployees[_companyId].activeEmployees, _employee);`, which would indicate whether the added employee was already in the set. Therefore, calling the function multiple times would lead to duplicate company id entries in `employeeToCompanyIds` after executing `employeeToCompanyIds[_employee].push(_companyId);`.

Show 8 more
LowMismatched Interface SignaturesFixed

Some contracts implement the `IVest` or `IModule` interface without explicitly inheriting from them. This can lead to mismatched function signatures as is the case here. Some of the functions are missing the `view` specifier, none of them specify a return type and none of the modules implement a `getPlan` function.

LowMissing Address Input ValidationFixed

It is important to validate inputs, even if they only come from trusted addresses in order to avoid human error. The following functions do not have proper validation of input parameters: - `Escrow.initialize` - `Vesting.initialize` - `{Escrow,CliffModule,LinearModule,StreamModule}.setVestingControllerAddress`

LowMissing Initializer CallsFixed

Not all of the initializers from the inherited contracts are called. While this might not lead to issues with the current version, this can change in the future.

LowName Clashes with OwnableFixed

The `EmployeeRegistry` contract has several functions related to company ownership that are simply referred to as `owner`. Since the contract also inherits from `Ownable`, this might lead to confusion.

LowNo Access Control on `EmployeeRegistry.addEmployee()` and `EmployeeRegistry.archiveEmployee()`Fixed

The `EmployeeRegistry` contract does not employ any access control for adding or archiving employees, enabling anyone to add arbitrary addresses as *active employees* to arbitrary company ids or mark them as inactive. As a result, the distinction between active and inactive employees is currently pointless.

LowRedundant Storage VariablesFixed

There are several member variables that contain identical values, one typed and one `address`. This is redundant since the address can be accessed by casting it like this: `address(cliffModule)`. List of redundant variables: ```solidity address public employeeRegistryAddress; address public cliffModuleAddress; address public linearModuleAddress; address public realtimeModuleAddress; address public escrowAddress; ```

LowSafeMath Library RedundantFixed

Starting from solidity version 0.8, overflows and underflows revert by default (see [docs](https://docs.soliditylang.org/en/v0.8.11/080-breaking-changes.html#silent-changes-of-the-semantics)), rendering the use of SafeMath unnecessary.

LowUnnecessary `onlyOwner` ModifierFixed

The `getVestingControllerAddress` view function has the `onlyOwner` modifier. Since the `vestingControllerAddress` variable is public anyway, this serves no purpose.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (8)

  • contracts/modules/BaseModule.sol
  • contracts/employee/EmployeeRegistry.sol
  • contracts/modules/LinearModule.sol
  • contracts/vesting/Vesting.sol
  • contracts/modules/CliffModule.sol
  • contracts/escrow/Escrow.sol
  • contracts/lib/Utils.sol
  • contracts/modules/StreamModule.sol

Part of LiquiFi’s security record

LiquiFi
Audits1 audit by 1 firmLast audit Jan 2022, by Quantstamp.
Critical findingsNo open critical findingsIn 1 report.
Open LiquiFi’s security record

Security firm

Quantstamp

236 projects reviewed · 332 reports · latest Oct 2026

See Quantstamp’s audits

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us