TrustNet Score
The TrustNet Score evaluates crypto projects based on audit results, security, KYC verification, and social media presence. This score offers a quick, transparent view of a project's credibility, helping users make informed decisions in the Web3 space.
Real-Time Threat Detection
Real-time threat detection, powered by Cyvers.io,
is currently not
activated
for this project.
This advanced feature provides continuous monitoring and instant alerts to safeguard your assets from potential security threats. Real-time detection enhances your project's security by proactively identifying and mitigating risks.
For more information, click here.
Security Assessments
Summary and Final Words
No crucial issues found
The contract does not contain issues of high or medium criticality. This means that no known vulnerabilities were found in the source code.
Contract owner cannot mint
It is not possible to mint new tokens.
Contract owner cannot blacklist addresses.
It is not possible to lock user funds by blacklisting addresses.
Contract owner cannot set high fees
The fees, if applicable, can be a maximum of 25% or lower. The contract can therefore not be locked. Please take a look in the comment section for more details.
Token transfer can be locked
Owner can lock user funds with owner functions.
Token cannot be burned
There is no burning within the contract without any allowances
Ownership is not renounced
The owner retains significant control, which could potentially be used to modify key contract parameters.
Contract is not upgradeable
The contract does not use proxy patterns or other mechanisms to allow future upgrades. Its behavior is locked in its current state.
Scope of Work
This audit encompasses the evaluation of the files listed below, each verified with a SHA-1 Hash. The team referenced above has provided the necessary files for assessment.
The auditing process consists of the following systematic steps:
- Specification Review: Analyze the provided specifications, source code, and instructions to fully understand the smart contract's size, scope, and functionality.
- Manual Code Examination: Conduct a thorough line-by-line review of the source code to identify potential vulnerabilities and areas for improvement.
- Specification Alignment: Ensure that the code accurately implements the provided specifications and intended functionalities.
- Test Coverage Assessment: Evaluate the extent and effectiveness of test cases in covering the codebase, identifying any gaps in testing.
- Symbolic Execution: Analyze the smart contract to determine how various inputs affect execution paths, identifying potential edge cases and vulnerabilities.
- Best Practices Evaluation: Assess the smart contracts against established industry and academic best practices to enhance efficiency, maintainability, and security.
- Actionable Recommendations: Provide detailed, specific, and actionable steps to secure and optimize the smart contracts.
A file with a different Hash has been intentionally or otherwise modified after the security review. A different Hash may indicate a changed condition or potential vulnerability that was not within the scope of this review.
Final Words
The following provides a concise summary of the audit report, accompanied by insightful comments from the auditor. This overview captures the key findings and observations, offering valuable context and clarity.
Smart Contract Analysis Statement
Contract Analysis
The DEOS-X vault contract is not a token. It is the on-chain custody and bookkeeping layer of a closed stablecoin yield fund for a small set of vetted investors. Whitelisted investors deposit USDT into three tranches that fill strictly in order, the first two once and the third again whenever capital is paid out of it, and every deposit mints a non-transferable LP token of the receiving tranche one to one with the capital placed. Investors leave by requesting a withdrawal per tranche. The owner, a Safe wallet that requires several approvals, pays each request with the capital and with interest taken from a pre-funded interest pot of that tranche. The amount of interest, any introducer fee and the timing of every payout are decided off-chain by the owner, because the fund invests the capital outside the blockchain and the contract only records what was deployed and what came back. The contract is not upgradeable, has no proxy and no separate setup call, and its stablecoin, tranche sizes and interest tier thresholds are fixed at deployment. A pilot vault with a total capacity of 20 USDT is live on BNB Smart Chain, and its deployed code was confirmed to match the reviewed source exactly, including the compiler fingerprint. The production vault had not been deployed at the time of review. The review found no critical and no high severity issues. The accounting, access control and payout rules were proven with symbolic analysis over bounded input ranges. For the order in which positions are consumed, the proofs hold with either the deposit sizes or the holding periods fixed, while the combined case did not finish within the time limit and rests on randomised testing. The rules were also stress tested with about 12.7 million randomised calls, most of them in repeated runs of the invariant test suites, including blacklisting and transfer fee scenarios of the Ethereum USDT contract, and the books never deviated from their intended state.
Ownership Privileges
Ownership has not been renounced and cannot be, because the renounce function is disabled. The owner role is held by a Safe wallet, and the picture is as follows:
- The owner can send any amount of the capital held by the vault to any address in a single transaction. There is no cap, no delay, no list of permitted destinations and no reserve for withdrawals already requested, and the amount is only recorded as deployed capital that is expected back.
- The owner funds the interest pots and can take unused interest back at any time. It also decides how much interest each withdrawal receives, so the return paid to an investor is entirely at its discretion within what the pot holds.
- The owner alone pays withdrawal requests, in any order and at any time, or not at all. Investors have no on-chain way to leave without the owner, because the LP tokens cannot be transferred or sold and there is no emergency or forced withdrawal path.
- The owner can pause the vault, which stops new deposits and new withdrawal requests. Payouts by the owner and cancellations by investors keep working while the vault is paused.
- The owner appoints and removes operators and changes the minimum deposit amounts. Operators, which are the portal's backend keys, maintain the investor whitelist and the introducer records, which the owner can also do, but they cannot move any funds.
- The owner cannot mint LP without a deposit, cannot move, burn or freeze an investor's LP other than by paying that investor's request, cannot change the stablecoin, the tranche sizes, the tier thresholds or a request's recorded split, and cannot send interest pot funds out through the capital transfer function.
- Ownership can only change in two steps, with the new owner accepting, and can never pass to an investor wallet. Staff roles and investor roles are kept strictly apart.
- On the live pilot the owner Safe has three signers and needs all three to sign, so a single lost key would freeze the vault permanently, and one of the signers is the account that deployed the contracts and uses its key for everyday transactions.
Security Features
The contract implements several positive security features:
- Every function that moves USDT is protected against reentrancy, payments out of the vault are made only after the books have been updated, and USDT is handled through a library that copes with the non-standard return values of the Ethereum USDT contract.
- Deposits, interest funding and capital returns are credited with the amount that actually arrived rather than the amount requested, so the books stay exact even if the stablecoin charges a transfer fee.
- Capital and interest are kept apart. Interest pots can never be sent out as capital, and the capital available to the owner is derived from the actual balance and can never be negative, so an unexpected loss of funds leaves the books readable instead of locking them.
- Each investor's deposits are recorded as dated positions and consumed oldest first, and the interest tier split of a withdrawal is frozen when it is requested, so neither the owner nor later activity can change it.
- A deposit can carry the split the investor expects across the tranches and reverts if another deposit changed it in the meantime, which protects investors from being pushed into a lower-rate tranche.
- The contract contains no inline machine code, no delegated calls, no self-destruct and no arithmetic with disabled overflow checks, and all capital amounts are bounded by the tranche sizes, which are capped far below any overflow range.
- A blacklisted or otherwise blocked investor affects nobody else, because no function iterates over other investors' funds and each payout touches only the paid investor's records.
- All configuration is set at deployment in one indivisible step, so there is no setup call that an outsider could race to take control of the vault.
Points Requiring Attention
The following matters deserve attention before the production vault is deployed and funded:
- The safety of every deposit rests entirely on the owner Safe and on the fund's off-chain processes. This is a deliberate trust assumption of the fund's design rather than a coding defect. A time lock with a published minimum delay, per-period limits and a fixed list of destinations for capital transfers and interest withdrawals, together with regular public reporting of deployed capital, would make the owner's actions bounded and observable without changing the fund's model. The pause should stay available without delay as an emergency stop, with a limit on how long it can last.
- The production owner Safe should have more signers than its threshold, with independent hardware keys held by different people, and none of them should be an account used for deployments or daily transactions.
- On Ethereum, the USDT contract has a transfer fee that is currently zero but can be switched on by its issuer. If that happens, every deposit made through the documented slippage protection would revert until the contract or the application is changed.
- On Ethereum, the USDT issuer can blacklist any address and can redirect the token's transfers, balances and approvals to a new contract of its choosing. If the vault itself were blacklisted, every payout and capital transfer would stop until the listing is lifted, and after a redirection the vault's token movements would follow rules outside the reviewed code. The redirection was not in use at the time of review. BSC-USD has none of these functions.
- The live pilot is not listed in the project's deployment records, and parts of the design documentation still describe an earlier rule of one pending withdrawal request per wallet instead of one per tranche. The team should publish every deployed address with its purpose and bring the documentation in line with the code. The pilot's LP tokens are covered in the separate statement for the token.
Note - This Audit report consists of a security analysis of the DEOS-X Stablecoin Yield Fund vault (DeosxVault) smart contract. This analysis did not include economic analysis of the fund's investment strategy, interest terms or off-chain operations. Moreover, we only audited the main contract for the DEOS-X team, and the LP token it creates is covered by a separate statement. Other contracts and systems associated with the project were not audited by our team, including the owner Safe, the investor portal and its backend, the deployment scripts and the testnet helper contracts. We recommend investors do their own research before investing.
Files and details
Findings and Audit result
low Issues | 2 findings
Pending
#1 low Issue
Deposits that follow the documented slippage guard always revert once the Tether transfer fee is on
deposit places the amount that actually arrived, which is correct, but compares each tranche's share against minPieces, and the NatSpec and the README tell integrators to pass the result of previewDeposit(amount) for the gross amount. Tether's Ethereum contract has a transfer fee that is currently zero but can be switched on by its owner. While it is on, the amount received is always smaller than the amount previewed, so the last tranche the deposit reaches gets less than its preview and the call reverts with PlacementChanged although nothing changed in between. The README's advice for that error, to preview again and ask the user to confirm the new split, produces the same preview and the same revert. Every deposit made through the documented path would therefore fail on Ethereum until the application is changed, and the only way to deposit would be to pass a zero guard and give up the protection the guard was added for. A test confirmed the revert for a deposit within one tranche and for one that crosses a tranche boundary, and randomised testing reached the same condition independently. The BSC deployment is not affected because BSC-USD has no transfer fee.
Pending
#2 low Issue
Centralization by design: the owner Safe alone decides whether and when investors get their capital back
Every movement of money out of the vault is an owner action with no on-chain limit. transferOut sends any amount of capital-side USDT to any address in one call, with no cap, no delay, no destination allowlist and no reserve for requests that are already pending. withdrawInterest returns any part of an interest pot to the owner at any time, so the separation between capital and interest only protects against mistakes, and fulfillWithdraw pays whatever interest the owner chooses. pause stops new deposits and new withdrawal requests, so while the vault is paused an investor cannot even record that they want to leave. Investors have no exit of their own: only the owner can pay a request, the LP tokens cannot be transferred or sold, and there is no forced withdrawal or emergency path if the owner stops acting or loses access. A test confirmed that the owner can send all capital out in one call, take the interest pot back and pause the vault, after which both pending payouts revert for lack of capital and new requests are refused. The documentation describes this custodial model as intended and the owner is a Safe rather than a single key, but the safety of every deposit rests entirely on that Safe's signers and on off-chain processes that the contract cannot enforce. This is a centralization risk and a trust assumption of the fund's custodial design rather than a coding defect, and the rating reflects what investors would lose if that assumption failed.
optimization Issues | 3 findings
Pending
#1 optimization Issue
Each position takes two storage slots where one would do
Position stores the amount as uint256 and the deposit time as uint64, so every position occupies two slots. The constructor caps every tranche size at the uint128 maximum, so a position can never exceed 128 bits and both fields fit into one slot. Each deposit piece would then create one new slot instead of two, which saves about 20,000 gas per piece, and every position read by requestWithdraw, fulfillWithdraw and positionsOf would cost one storage read instead of two.
Pending
#2 optimization Issue
LP token addresses and tranche sizes are read from storage although they never change
The three LP token addresses are kept in a storage array and the tranche sizes inside the Tranche structs, but neither can change after construction. deposit, fulfillWithdraw, previewDeposit, trancheInfo and the recipient check in setIntroducer read them from storage every time, at 2,100 gas for each first read in a transaction. A fixed-length array cannot be declared immutable or constant, but three immutable addresses and three immutable sizes, selected by an internal function that takes the tranche index, keep the same structure without any storage reads.
Pending
#3 optimization Issue
Request amounts use full storage words although they are bounded by 128 bits
A Request occupies eight storage slots. Its first slot is already packed, but the amount, the three tier buckets, the interest and the fee each take a full slot, although the amount and the buckets can never exceed a tranche size, which is capped at 128 bits. Storing the amount and the buckets as two pairs of 128-bit values saves between about 4,000 and 44,000 gas per request, depending on how many tier buckets are non-zero, and packing the interest with the fee turns the fee write in payIntroducerFee into an update of an existing slot.
informational Issues | 9 findings
Pending
#1 informational Issue
The live pilot's owner Safe needs every signer, and one signer is the deployer's everyday account
The vault deployed on BNB Smart Chain at 0xffDADbA1A634f17EAa9f23625df57c2743c8522B is owned by the Safe 0xf6ae15BB022886832814F3980303A0d9e65E5dE5, version 1.5.0, with three owners, a threshold of three, no modules and no guard. A threshold equal to the number of owners gives no tolerance for a lost or unavailable key. If any one signer loses access, the Safe can never sign again, and because only the owner can pay requests, return capital or hand the vault over, every investor's capital would be frozen permanently. One of the three signers, 0xa480087F9083A9427b8723f177a16761a5aF9fC4, is the account that deployed the vault. It has sent more than 180 transactions and carries an EIP-7702 delegation to a smart account implementation, so its key is in day-to-day use and its behaviour also depends on delegated contract code. The same Safe address is used on Sepolia with a threshold of two, so the signer configuration behind one address differs between chains, and the README names this address as the owner of the future production vault. The signers' key management itself is outside the scope of this review, so these observations come from the Safe's on-chain configuration and history. A threshold equal to the number of owners is an availability risk rather than a theft risk, because no single key can move funds, and the pilot's capacity is limited to 20 USDT. The finding is therefore informational for the pilot, and its recommendations become requirements for the production Safe.
Pending
#2 informational Issue
The live BSC pilot is missing from the deployment records
A pilot vault built from the audited commit has been live on BNB Smart Chain since 25 October 2026 at 0xffDADbA1A634f17EAa9f23625df57c2743c8522B, with the pilot terms the README describes: tranches of 2, 3 and 15 USDT, minimums of 0.1 and 0.01 USDT and tiers after 60 and 90 minutes. It is already in use: the owner Safe has appointed two operators, an investor wallet has been whitelisted and the Safe has sent that wallet BSC-USD. The README describes how to deploy a pilot but lists no pilot address, and its BNB Smart Chain table still says the vault and the Safe are not deployed. Other passages are older than the move to BSC: the deployment section of the README and the audit context say the deploy script only targets Ethereum mainnet and Sepolia and requires a USDT with 6 decimals, the audit context names Ethereum mainnet as the target chain, and the design document plans an Ethereum-only launch. The only vault the documents list, on Sepolia, predates the latest fixes and does not match the audited code. Investors and integrators therefore have no authoritative record of which vault is the fund, which makes reconciliation harder and leaves room for confusion with look-alike deployments. The pilot's LP tokens, which carry the production names and are not verified on the explorer, are covered in the LP token report.
Pending
#3 informational Issue
The pinned compiler release has known defects that later releases fix, none of which affects this code
The contract is pinned to solc 0.8.30. The official bug list records eight defects present in that release and fixed between 0.8.32 and 0.8.38, one rated high, two medium, two low to medium, two low and one very low. Each was checked against this code and none is reachable. The build uses the legacy pipeline rather than the IR pipeline, the code declares no transient state variables, defines no custom storage layout, has no recursion, never deletes an element of a bytes array in memory, does not rely on the order of named parameters in a require with a custom error, and the inline assembly in the libraries does not contain the variable reuse pattern that the remaining optimizer defect needs. The deployed pilot is therefore not affected. The same compiler setting applies to the LP token contract and is reported separately for it.
Pending
#4 informational Issue
Tokens other than USDT that reach the vault cannot be recovered
The vault only ever moves its configured USDT. Any other token sent to it by mistake, such as a different stablecoin, a bridged copy of USDT or an airdrop, stays in the contract permanently because no function can transfer it. The contract has no receive or fallback function, so the native coin can only arrive by force, and it would be locked in the same way. Plain USDT transfers are not affected, because they raise capital-side cash and the documentation describes how the owner handles them.
Pending
#5 informational Issue
transferOut accepts recipients that can never spend the USDT it sends
transferOut refuses the zero address and the vault itself but accepts the USDT contract and the three LP token contracts. USDT sent to any of them is lost, because the LP token has no function that moves tokens it holds and the Tether contract does not return tokens sent to it. setIntroducer refuses exactly these addresses for the same reason, so the two checks are inconsistent. A mistaken destination in a Safe transaction would destroy capital that investors are owed, while the vault would keep recording it as deployed capital that is expected back.
Pending
#6 informational Issue
The starting configuration is not in the event log
The constructor sets the minimums, the tier thresholds, the tranche sizes and the three LP token addresses without emitting an event, so the starting configuration cannot be read from logs, and MinimumsSet only appears if the minimums are changed later. Indexers that rebuild the books from the event history, which the README also recommends for the admin queue, have to read these values through calls instead. WithdrawCancelled records only the request id and the owner, but the tranche and the amount are in the WithdrawRequested event with the same request id, so no information is lost and an indexer only has to join the two. The contract's behaviour is not affected.
Pending
#7 informational Issue
List views return whole sets without pagination
pendingRequests and feesDue return every element of their sets, and positionsOf returns every open position of a wallet in a tranche. The contract does not bound the first two: requests stay pending until the owner pays them or the investor cancels, with up to three per investor, and fees stay due until the owner pays or closes them. Positions per wallet are bounded by the minimums under the documented operating rule. Node providers cap the gas available to read-only calls, so a backlog of tens of thousands of entries would make the full-list calls fail, and any dashboard or back-end process that depends on them would stop working although the contract itself keeps operating. With the fund's small investor base and regular payouts that size is unlikely to be reached, so this is a robustness point rather than a present limit.
Pending
#8 informational Issue
Several public functions and state variables have no NatSpec
pause, unpause, trancheInfo and requestCount, and the public state variables TRANCHE_COUNT, usdt, openingMinimum, topUpMinimum, deployedCapital and totalInterestPot, have no NatSpec, while the rest of the contract is documented thoroughly. The gaps are where readers most need context, for example which entry points pause actually stops and that payouts and cancellations keep working while the vault is paused. The LP token contract has similar gaps, reported separately for it.
Pending
#9 informational Issue
Parts of the design documents still describe one pending request per wallet, while the code allows one per tranche
The code allows one pending withdrawal request per wallet in each tranche, so up to three per wallet: openRequestOf is kept per wallet and tranche, and a new request reverts with RequestPending only if the same tranche already has one. The NatSpec, the README, the design document's description of positions and its decision log, and the request lifecycle in the audit context all say the same. Four passages still describe the earlier rule of one request per wallet. The design document's table of views says pendingRequests holds at most one request per investor. Its testing section describes a unit test in which a request in another tranche reverts with RequestPending, although the team's test asserts that such a request succeeds. Its invariant list sums a wallet's reserves across all tranches to the amount of its single request. The audit context lists one pending request per wallet among the deliberate design choices. The contract is not affected, but an admin interface or back-end process built from these passages could key pending requests by wallet and then show or pay only one of a wallet's requests, and estimates based on one request per investor understate the length of pendingRequests by up to a factor of three.