As of 3 Nov 2026, Epoch Island has 2 audit reports by 1 security firm on Trustblock; the latest is by Quantstamp, dated 7 Nov 2023. Those reports list no open critical findings.
Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| Epoch Island - LP Pool | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
| Epoch Island - vEPOCH Token | Security firm: Quantstamp | Date: | Scope: Files named in the report | No open critical findings | Open · certificate.quantstamp.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 1 |
| Medium | 0 | 2 | 3 |
| Low | 4 | 16 | 6 |
The protocol's current architecture does not account for the unique behaviors of various specialized ERC20 tokens, including interest-bearing, deflationary, and rebasing tokens. These tokens are characterized by their dynamic balance mechanisms, where the token balance can autonomously change over time due to factors like transaction fees, re-distribution, or balance adjustments. This aspect contradicts the protocol's underlying assumption of static token balances, potentially leading to inaccurate calculations or unintended interactions within the contract. Additionally, the protocol's open design, which allows liquidity providers (LPs) to utilize any ERC20 token, poses a significant risk. This unrestricted approach could enable LPs to introduce malicious tokens, crafted specifically to exploit the contract's mechanics or defraud takers. Such tokens might be engineered to perform a rug-pull – abruptly devaluing or making the tokens non-transferable after a transaction – or to otherwise deceive and manipulate the takers. This vulnerability necessitates a more stringent approach to token vetting and validation within the contract, to safeguard against these deceptive strategies and ensure the integrity of transactions.
Open the reportWhenever a `rewardToken` or `depositToken` is transferred, they may fail without reverting. Depending on the context, this may lead to inconsistencies, users losing funds, or the contract losing funds. The reason is that calls to `transfer()` and `transferFrom()` are not checked for returning true. While many ERC20s will revert upon transfer failure, some ERC20s will return false in adherence to the ERC20 specification. If `rewardToken` or `depositToken` will return false on failure the transfer will silently fail. The return value is not checked in the following places: 1. `Vepoch.sol#L78`. 1. `Vepoch.sol#L108`. 1. `Vepoch.sol#L130`. 1. `Vepoch.sol#L146`. 1. `Vepoch.sol#L192`. 1. `Vepoch.sol#L210`. 1. `Vepoch.sol#L215`. 1. `Vepoch.sol#L234`. 1. `Vepoch.sol#L252`.
Open the reportThe owner of the contracts can call `transferOwnership()` to transfer the ownership to a new address. If an uncontrollable address is accidentally provided as the new owner address, then the contract will no longer have an active owner, and functions with the `onlyOwner` modifier can no longer be executed.
Open the reportTwo values were hard-coded into the code and, therefore, limited the flexibility: 1. The maximum LP fee that can ever be set is hard-coded into `supply()`. 1. The maximum protocol fee is hard-coded in `setProtocolFee()`.
Open the reportIt is important to validate inputs, even if they only come from trusted addresses, to avoid human error. The following functions do not have a proper validation of input parameters: 1. `EpochUpsidePoolV1.supply()`: 1. `_exchangeRate` not checked to be non-zero. Clarify if this is intended. 1. `_startDate` and `_endDate` not checked to be non-zero. 1. `_startDate` and `_endDate` not checked to be chronological. 1. `_startDate` and `_endDate` not checked to have a reasonable lower bound difference between each other. 1. `_feeBp` not checked to be non-zero. Clarify if this is intended. 1. `EpochUpsidePoolV1.setProtocolFee()`: 1. `_protocolFeeRecipientAddress` not checked to be different from `addresss(0x0)`. 1. `_protocolFeeMaxBp` not checked to be non-zero. Clarify if this is intended. 1. `EpochUpsidePoolV1.claimPositionFees()`: `_tokenAddresses` not checked to be the same length as `_tokenAmounts`.
Open the reportIf the owner renounces their ownership, all ownable contracts will be left without an owner. Consequently, any function guarded by the `onlyOwner` modifier will no longer be able to be executed.
Open the reportCertain contracts have state variables, e.g., `owner`, which provide specific addresses with privileged roles. Such roles may pose a risk to end-users. For a detailed list of roles per contract, see the following list: The `EpochUpsidePoolV1.sol` contract contains the following privileged roles: 1. `_owner` (`Ownable` inheritance), as initialized during the execution of `constructor` to `msg.sender`: 1. Renounce the role (**and thereby prevent any future calls to the following listed functions!**) by calling `renounceOwnership()`. 1. Transfer the ownership to an arbitrary other address by calling `transferOwnership()`. 1. Change the protocol fee (up until `7001`, 7,001%) and fee receiver address by calling `setProtocolFee()`. A malicious or compromised owner could, in a worst-case scenario, unexpectedly change the protocol fee from zero to up to 7.001%.
Open the reportThe `deposits` mapping will begin at index one upon first `deposit()` call, leaving the zero'th index empty. This is because `depositCount` is incremented before setting the next `deposits` value. Particularly, if `deposits` is changed to an array for gas efficiency reasons, it is recommended to commence at index zero.
Open the reportThe `owner` may (accidentally) prevent further user deposits by setting `maxDepositDuration < 60`. This would lead to `deposit()` always reverting as `_duration > 59` and `_duration <= maxDepositDuration` cannot hold true at the same time for `maxDepositDuration < 60`. The root issue is that `setMaxDepositDuration()` does not enforce a lower bound on `maxDepositDuration`. Furthermore, the `owner` can (accidentally) set a too-low `maxDepositDuration` and lock that value forever by calling `setMaxDepositDurationLocked()`. This would lock out any future calls to `setMaxDepositDuration()` to undo such a setting
Open the reportIn order to validate the proper deployment and initialization of the contracts, it is a good practice to emit events. Also, any important state transition can be logged, which is beneficial for monitoring the contract, and also tracking eventual bugs, or hacks. Below we present a non-exhaustive list of events that could be emitted to improve the application management: 1. `Vepoch.addRewardTokens()`: `rewardIndex`. 1. `Vepoch._updateRewards()`: `earned[]` and `rewardIndexOf[]`. 1. `Vepoch.setAuthorisedLocked()`: `authorisedLocked`. 1. `Vepoch.setMaxDepositDurationLocked()`: `maxDepositDurationLocked`. 1. `Vepoch.setForfeitRedirectionAddressLocked()`: `forfeitRedirectionAddressLocked`. 1. `Vepoch.setForfeitRedirection()`: `forfeitRedirectionAddress`.
Open the reportOnce the `owner` gives up control over authorising addresses by calling `setAuthorisedLocked()`, all authorised addresses will forever be able to transfer vEPOCH tokens. While the `owner` can "unauthorize" addresses, they may not do so for all intended addresses before giving up control.
Open the reportThe owner of the contracts can call `transferOwnership()` to transfer the ownership to a new address. If an uncontrollable address is accidentally provided as the new owner address then the contract will no longer have an active owner, and functions with the `onlyOwner` modifier can no longer be executed.
Open the reportThe reward mechanism is supposed to be an incentive for users to lock their `depositToken`. However, their received rewards depends on the amount of rewards provided through `addRewardTokens()` during their lock period. This uncertainty may not pose a stable incentive for users to deposit and lock their tokens.
Open the reportAlthough this issue is only for authorized users, when veToken is transferred, the `rewardStakingPower`, `rewardIndexOf` and the deposits are not changed accordingly. This could cause the inability of authorized users in `withdraw()` and `withdrawForfeit()`. This would also lead to an incorrect calculation of rewards.
Open the reportIt is important to validate inputs, even if they only come from trusted addresses, to avoid human error. The following functions do not have a proper validation of input parameters: 1. `Vepoch.constructor()`: `_depositToken` and `_yieldToken` not checked to be different from `address(0x0)`. 1. `Vepoch.setMaxDepositDuration()`: `_newMaxDepositDuration` not checked to be greater than `59` or any other reasonable lower bound.
Open the reportIf the owner renounces their ownership, all ownable contracts will be left without an owner. Consequently, any function guarded by the `onlyOwner` modifier will no longer be able to be executed.
Open the reportSpecific contracts have state variables, e.g., `owner`, which provide certain addresses with privileged roles. Such roles may pose a risk to end-users. For a detailed list of roles per contract, see the following list: The `Vepoch.sol` contract contains the following privileged roles: 1. `_owner` (`Ownable` inheritance), as initialized during the execution of `constructor` to `msg.sender`: 1. Renounce the role (**and thereby prevent any future calls to the following listed functions!**) by calling `renounceOwnership()`. 1. Transfer the ownership to an arbitrary other address by calling `transferOwnership()`. 1. Forever prevent any future calls to function `setAuthorised()` for changing transfer-authorized addresses by calling `setAuthorisedLocked()`. 1. Forever prevent any future calls to function `setMaxDepositDuration()` for changing the maximum deposit time (up to 315576000 seconds or 3652,5 days) by calling `setMaxDepositDurationLocked()`. 1. Forever prevent any future calls to function `setForfeitRedirection()` for setting/unsetting a forfeit reward redistribution address by calling `setForfeitRedirectionAddressLocked()`. 1. Add/Remove addresses from the `veToken` transfer whitelist (only addresses in that list may transfer `veToken`s via `transfer()`/`transferFrom()`) as long as this function is not locked via `setAuthorisedLocked()` by calling `setAuthorised()`. 1. Change the maximum deposit duration (up to 315576000 seconds or 3652,5 days) as long as this function is not locked via `setMaxDepositDuration()` by calling `setMaxDepositDuration()`. 1. Set/Unset a forfeit reward redistribution address as long as this function is not locked via `setForfeitRedirectionAddressLocked()` by calling `setForfeitRedirection()`. A malicious or compromised owner could in a worst-case therefore: 1. Arbitrarily allow/disallow `veToken` transfers by adding/removing addresses from the authorized whitelist, as long as `setAuthorisedLocked()` has not been called. 1. Denial the service of any future deposits by setting a maximum deposit time (`maxDepositDuration`) of less than 60 seconds, as long as `setMaxDepositDurationLocked()` has not been called.
Open the reportIf an address or third party is authorized to transfer vEPOCH tokens, they may not be able to recover their locked deposits when they transfer vEPOCH tokens. Withdrawing entire deposits requires providing the initially minted amount of vEPOCH tokens. If some of them are transferred but cannot be obtained anymore, (parts of) the deposits cannot be recovered.
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="epoch-island"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
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