Audits
You can find on this page the list of audits done.
Codebase overview
The smart contract suite is composed of four primary components. The scope of this audit will be strictly limited to the on-chain business logic.
In-Scope Components:
Core Protocol Logic: The primary implementation of the contract's business rules and state transitions.
Points System Module: The logic governing the calculation, distribution, and management of user points.
Out-of-Scope Components:
View & Helper Functions: Read-only functions intended for frontend data retrieval (e.g., functions that return a user's loan history). These do not alter contract state and are excluded.
Testing Suite: All unit tests, integration tests, and deployment scripts are considered development artifacts and are not part of this review.
Below you can find precisely the audit scope. The provided Lines of Code (LOC) count is based on the current commit in the repository. The core logic under review is located in lib.cairo, specifically lines 1-474, which contain the interface definition and implementation of the IMyCode trait.
Audit scope
lib.cairo
1 - 474 (474LOC)
The smart contract
utilities.cairo
Whole file (107 LOC)
Some utilities for the code
datastructure.cairo
Whole file (48 LOC)
The datastructures used
constants.cairo
Whole file (45 LOC)
Just some constants
NOT in scope
lib.cairo
475 - 591
Only reader functions, not used in the smart contract, only in the frontend
mock_erc20.cairo
Whole file
Is a mock erc20 contract. Only used in tests, not in the contract
tests/*
Every tests
Because these are tests
List of audits done so far
June 2025
100%
6ca4e73
00cb14a
Audit by Hakunamatata
We recently had our codebase audited by Hakunamatata. Here are their relevant links:
X (formerly Twitter): https://x.com/Hakuna29997288
Code4rena: https://code4rena.com/@hakunamatata
Notably, they secured the top position in a major Cairo codebase audit, as detailed here: https://x.com/Hakuna29997288/status/1916978911185146088
The audit was performed on the following codebase commit:
We implemented a first round of fixes in this commit:
However, this initial fix introduced some new errors, which necessitated a second round of corrections:
While we did not produce a formal audit report, we are providing this summary of the issues found in response to community requests. Please note that the line numbers referenced below correspond to the audited commit linked above, not the repository's latest commit.
(This summary was written by our team (and gemini 🤖), not the auditor.)
Mismanagement of Disabled Assets (2 Issues)
"After an asset is disabled, users cannot withdraw it from the protocol, leading to a loss of funds for the users." This was a valid issue. In the protocol, assets are whitelisted for use. If an asset is deprecated or compromised, the admin is supposed to disable it. When the code was written, we had not fully implemented the asset disabling functionality. As a result, the withdraw function required assets to be whitelisted (L130), which was no longer the case once they were disabled. We could have upgraded the contract to allow withdrawals after users reported it, but still, better to fix it now.
"If both lendable and borrowable assets get disabled in the protocol, offers with these assets can still be matched." This was also a very valid issue. The protocol only allows loans between assets of the same category (e.g., ETH and USDC are in different categories). This is checked at L212. Unfortunately, when two assets are disabled, their category resets to 0, allowing them to be matched against each other again. This is dangerous because a disabled asset may not be worthless. For example, if a wrapped asset were to depeg but still retain some value, it could be borrowed using another disabled, valueless asset, potentially draining significant value from the protocol.
Fixes: Both issues have now been resolved.
First, we introduced a proper function to disable assets (L436). Initially, this only set the asset's category to 0. Following the auditor's recommendation, we improved this in the second fix to reset all of the asset's parameters (L440-443).
For the first issue, the requirement in withdraw that an asset's category not be 0 has been removed (L130).
For the second issue, we now check that both assets are of the same category and that this category is not 0 (L227-L231).
Incorrect Point Calculation (1 Issue)
"Users can increase their user points for free by creating borrow and lend offers with a minimal duration of 0, matching them, and repaying them immediately. The time_diff is 0, which means they do not pay any fees or interest, but their user points get increased." This was a very valid issue. Previously, user points were awarded based on the loan volume. This allowed users to create large loans for themselves, repay them instantly with a duration of 0 (incurring no fees or interest), and accumulate an unlimited number of points.
Fix: This issue is now fixed. Points are now awarded proportionally to the fee paid, rather than the loan amount (L343-344).
Missing Admin Functionality (1 Issue)
"There is no function to transfer collected fees from the contract." This was a valid issue. The protocol collected a 1% APR fee which was correctly accounted for, but there was no function to allow the team to withdraw these collected fees.
Fix: This has been fixed by adding a fee withdrawal function (L140-152).
Miscellaneous Issues (4 Issues)
"Users can grief borrowers by matching their offers with amount = 0, leading to match_offers where collateral_amount = 1 and lended_amount = 0." This is a valid issue that we have acknowledged in our documentation: Known Issues - Market Maker Griefing.
"match_offer and make_borrow_offer/make_lend_offer do not check if the amount passes the checks inside to_assets_decimals." This is another valid issue related to dust amounts that we have acknowledged here: Known Issues - Dust Annoyance.
"If an offer is at a given Y APY, another offer can be set to be at Y+epsilon APY, effectively creating an unfair auction where users need to constantly adjust their offer if they want to be the best offer." This was a valid point. If a user creates a borrow offer at 5% APY, another user could create one at 5.00000001% APY to become the best offer. This creates an "unfair auction" scenario where market makers must constantly micro-adjust their rates. Other protocols solve this by enforcing discrete rate steps (e.g., Uniswap fee tiers).
Fix: We fixed this by enforcing rate steps for the APR (L158 and L188 of the first fix). However, we made two mistakes in the initial implementation:
We mistakenly used LTV_01_PERCENT instead of APR_01_PERCENT, which was corrected in the second round of fixes (L158 and L188).
The minimum APR was set to 0.01%, which is not a multiple of the 0.1% step. We fixed this by raising the minimum APR to 0.1% (L31 of constants.cairo in the second fix).
"Even though a borrower has a minimal duration = X, when their offer gets matched the minimal_duration can be bigger," and "Borrower has no control over how much they will pay for the loan." This is acknowledged as a core part of the protocol's design. The final loan parameters are a combination of the lend and borrow offers. If a user allows for a wide duration range (e.g., [1 day, 1 year]), any loan within that range that meets the minimum duration can occur. Users must be careful when setting wide parameters.
Action: We recognized that our documentation and frontend were lacking in this area. We have since updated our documentation and improved frontend warnings to better inform users of this behavior.
Last updated