As of 30 Oct 2026, RTLabs has 2 audit reports by 1 security firm on Trustblock; the latest is by Resonance Security, dated 7 Feb 2025. Those reports list no open critical findings.
Record last updated . It holds audits and findings, contracts and incidents up to Oct 2026. Trustblock does not review code.
Newest first
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| RTLabs | Security firm: Resonance Security | Date: | Scope: Not recorded (see the report) | No open critical findings | Open · IPFS |
| RTLabs | Security firm: Resonance Security | Date: | Scope: Not recorded (see the report) | 1 critical finding, fixed | Open · IPFS |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 1 |
| High | 0 | 0 | 1 |
| Medium | 0 | 0 | 5 |
| Low | 3 | 0 | 8 |
The protocol implements bridging features between layer-1 and layer-2 blockchains to sync the rate at which shares of tokens are minted for the users. By calling the function syncRate() on the contract DepositsManagerL1, a message is sent to several layer-2 chains to sync their rates with the new rate calculated on the layer-1 chain. On the layer-2 chains, when such a message is received, the rate is updated while the variable rateSyncBlock tracks the last block of when the updated occurred.When minting tokens on a layer-2 chain, the function getConversionAmount() is called to calculate how many shares of the token should be minted for the user based on the current rate. The only verification being made simply guarantees that a single rate update was performed in the past. It does not guarantee that the rate is up to date. This opens up the possibility of financial attacks by malicious users that can take advantage of stale rates to take advantages during minting.
Open the reportThe function deposit() is marked as payable, allowing for transfer of native Ether into the smart contract. This function further calls the internal function _deposit() while providing _amountIn as an input parameter. This effectively means that the amount of minted tokens for the user will solely depend on the provided input parameter _amountIn. If the user mistakenly sends Ether when calling this function, they will not be able to mint those funds as tokens.
Open the reportThe fees used within the protocol are not sufficiently validated, and are therefore not restrictive enough of their upper and lower bounds. The fees can both be set to the max value and to the value 0, both of them being dangerous to the protocol that can eventually cause loss of funds, or confidence from users of the platform.
Open the reportThe variable _minter does not implement a setter function. If the entity responsible for controlling the address pointed by the variable _minter becomes compromised, the entire protocol may be at risk, and may ultimately lead to total loss of funds.
Open the reportThe Demether protocol defines a specific intended flow for adding liquidity to the protocol. Namely, in order to deposit ETH, users will need to call the depositETH function implemented in the DepositsManagerL1 contract. This function implements appropriate checks, fee calculation and token minting before it calls the addLiquidity function defined in the LiquidityPool contract. The addLiquidity function is mostly responsible for creating shares in the LiquidityPool contract. However, the addLiquidity function does not implement any access control that would allow it to be executed only if called by DepositsManagerL1 contract. As a consequence, anyone can call the addLiquidity function directly effectively bypassing the accounting done in DepositsManagerL1.Similarly, the processLiquidity function defined in the LiquidityPool contract can be called directly, without calling the processLiquidity function defined in the DepositsManagerL1 contract.
Open the reportThe local minting feature of both DepositsManagerL1 and DepositsManagerL2 contracts does not require a fee, since the fee should only be used for bridging purposes. However, it is possible to send a non-zero fee via the input parameter _fee, effectively allowing for users to lose unnecessary funds.
Open the reportThe LiquidityPool contract is implementing a receive function. This indicates that this contract can successfully receive a native balance transfer. However, the fact that this receive implementation is actually empty makes such transfers be out-of-flow. Such transfers can lead to vulnerabilities related to the accounting in the extreme cases. However, they also can cause an inherent loss for any legitimate user who was tricked or made a genuine mistake. Such transfers will not be accounted for accordingly by the contracts so users will lose their native tokens without being able to use the protocol.
Open the reportSmart contract upgradeability using OpenZeppelin's standard, requires contracts to use initializer functions. The function to use on child contract should be named initialize() and should possess the initializer modifier, while parent contracts should have _init() and _init_unchained() functions with the modifier onlyInitializing, where there would otherwise be constructors called automatically on non-upgradeable smart contracts.The initializer functions on the parent smart contracts are meant to be initialized by the child contract, as such, their visibility can be constrained to internal. The following functions do not implement the proper visibility to encourage a principle of least privilege:The visibility of the function __OwnableAccessControl_init() is incorrectly set as public.
Open the reportThe function initialize() on the upgradeable smart contract LiquidityPool does not call its parent's OwnableAccessControl initializer function __OwnableAccessControl_init(). Instead, it is calling __Ownable_init().While it is possible to call some parents' initializer functions out of order or higher in the inheritance chain, it is not recommended to do so as it will lead to development errors and undefined behavior within the protocol.
Open the reportThe functions _init() and _init_unchained() are an implementation of OpenZeppelin's Upgradeable Proxies design standard and are used to serve as the constructor of upgradeable contracts. Despite being both used as initializers, they possess slightly different use cases to correctly implement the analog linearization of smart contract constructors.The function _init() should be used and implemented to embed the linearized calls to all parent initializers. The function _init_unchained() should be used to perform the initialization of the variables for the current contract only.As a consequence of this design, it is possible that, due to the lack of automatic linearization, the calling of _init() functions initializes the same contract multiple times. For these cases, the _init_unchained() function may be used manually to avoid double initialization, however it will break the upgradeability design and compatibility between smart contracts on the blockchain.Additionally, the inherited smart contracts should be initialized according to their inheritance order, from the most base-like to the most derived.There are multiple instances where contracts do not follow the design standard in the following account:_init_unchained() function is not used.Initialization according to inheritance order is incorrect
Open the reportThe function depositETH() does not validate the Ether received from the user identified within the global variable msg.value. This variable is then used in an arithmetic operation along with another input variable _fee, which is controlled by the user. This effectively means that it may be possible for a user to trigger an integer underflow condition.
Open the reportThroughout the DepositsManagerL2 smart contract the messenger state variable is used to perform external calls. However, this variable is not validated against the Zero Address, allowing for undefined behavior within the protocol.
Open the reportThroughout the LiquidityPool smart contract the sfrxETH state variable is used to perform external calls. However, this variable is not validated against the Zero Address, allowing for undefined behavior within the protocol.
Open the reportThroughout the DepositsManagerL1 and DepositsManagerL2 smart contracts the token state variable is used to perform external calls. However, this variable is not validated against the Zero Address, allowing for undefined behavior within the protocol.
Open the reportThroughout the source code of the protocol, there are several instances where uint256 variables are being compared to 0. The comparison of these variables being lesser than or equal to 0 is redundant as uint256 variables can only contain 0 or positive values.
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="rtlabs"></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