O2Ramp Info

O2Ramp is the world's first Exchange and Earn platform powered by AI. Empowering individuals, retailers, and institutions to earn from the global currency economy.

O2Ramp Logo

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.

50.39
Poor Excellent

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

"Static Analysis Dynamic Analysis Symbolic Execution SWC Check Manual Review"
Contract address
N/A
Network N/A
License N/A
Compiler N/A
Type N/A
Language Solidity
Onboard date 2026/06/12
Revision date 2026/06/23

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.

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:

  1. Specification Review: Analyze the provided specifications, source code, and instructions to fully understand the smart contract's size, scope, and functionality.
  2. Manual Code Examination: Conduct a thorough line-by-line review of the source code to identify potential vulnerabilities and areas for improvement.
  3. Specification Alignment: Ensure that the code accurately implements the provided specifications and intended functionalities.
  4. Test Coverage Assessment: Evaluate the extent and effectiveness of test cases in covering the codebase, identifying any gaps in testing.
  5. Symbolic Execution: Analyze the smart contract to determine how various inputs affect execution paths, identifying potential edge cases and vulnerabilities.
  6. Best Practices Evaluation: Assess the smart contracts against established industry and academic best practices to enhance efficiency, maintainability, and security.
  7. 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 DepositCashierV2 contract implements a deposit and on-ramp cashier: users deposit ERC20 tokens against a back-end-signed authorization, permissioned operators later pay those funds out through distribution and split functions, and the owner manages the signer, the operator set, a pause switch and emergency fund collectors. While the overall design follows common patterns on Ethereum, a few areas need attention:

  • The deposit authorization signature has no single-use protection, so the same signature keeps working until it expires and can fund more than one deposit.
  • The functions that move funds out are not tied to recorded deposits and have no cap, so any privileged key can move the entire balance to any address. This is acceptable only under a clearly documented custodial model with fund movement placed behind a multi-signature wallet and a timelock.
  • Several inputs are under-validated: a token address is not confirmed to be a real contract, which can record deposits that never actually transferred funds, fee-on-transfer tokens are credited at the requested amount rather than the amount received, and an empty payout batch can permanently consume an identifier.
  • Operational safeguards that are expected for a custody contract are missing, including a two-step ownership handover, a timelock on sensitive actions, and a clean revocation of operator rights when ownership changes.

Remediation Status

The findings were shared with the DepositCashierV2 team, who responded to the two medium and seven low issues. No issue was withdrawn during this round, and a response that accepts a risk changes the status of a finding rather than its severity. The current standing is summarised below, and the detailed per-finding responses and auditor assessments are recorded in the full report.

  • Replayable deposit authorization (medium): the team noted that signatures are issued by the back-end. This addresses how a signature is created, not how it can be resubmitted on-chain, so the finding remains open at medium severity. The recommended fix is a single-use nonce recorded on-chain, together with a short expiry and crediting by the unique on-chain log identifier.
  • Privileged roles can move all custodied funds (medium): accepted as the intended custodial model, and ownership is intentionally retained so the owner can rotate the signer and recover stuck funds. The severity is kept at medium until the owner and operator keys are confirmed to be multi-signature wallets and fund movement is placed behind a timelock, at which point it can be lowered.
  • Low-severity findings: acknowledged. Some are accepted as intended design, such as setting the signer after deployment and keeping the owner and operator roles separate, and others are managed by the back-end, such as the batch sizes, an approved stablecoin-only token list, and the avoidance of empty payout batches. These are scheduled for follow-up in a later phase. Inexpensive on-chain hardening is still recommended for the constructor signer check, the token code check and the empty-batch check before deployment.

Ownership Privileges

The ownership of the contract has been assigned to the deploying account by default, with a single-step transfer mechanism and no timelock. The owner retains full privileges including:

  • Rotating the signer address that authorizes user deposits.
  • Granting and revoking operator permission, which controls who can distribute funds, split funds and pause deposits.
  • Pausing deposits and sweeping any token or Ether balance out of the contract through the emergency collectors.
  • Approving the operators who can move custodied funds to arbitrary receivers without an on-chain link to specific deposits.
  • The owner cannot mint, burn or change the supply of any asset, because this is not a token contract.
  • The owner cannot blacklist or freeze individual user balances; the only switch available is a global pause on new deposits.
  • The owner cannot upgrade the contract, since it uses no proxy and has no upgrade mechanism, so the logic is fixed once deployed.
  • The owner cannot charge a protocol fee on deposits, as there is no fee mechanism of any kind.

Security Features

The contract implements several positive security features:

  • The deposit function is protected by a reentrancy guard, and a test with a hostile token confirmed that re-entry into the deposit path is blocked.
  • Deposits are gated by a signature that includes the chain identifier and the contract address, which prevents the same authorization from being reused on another chain or another deployment, and the depositor address is fixed inside it so a third party cannot use someone else's authorization.
  • The distribution and split functions mark a batch as processed before sending any funds, which prevents the same batch from being executed twice.
  • The compiler version is fixed rather than floating, and the access control checks were verified to hold for unauthorized callers across extensive automated and exhaustive testing.

Note - This Audit report consists of a security analysis of the DepositCashierV2 smart contract. This analysis did not include economic analysis of the contract's tokenomics. Moreover, we only audited the main contract for the DepositCashierV2 team. Other contracts associated with the project were not audited by our team. We recommend investors do their own research before investing.

Files and details

Findings and Audit result

medium Issues | 2 findings

Pending

#1 medium Issue
Deposit authorization signature can be replayed
deposit_cashier_v2.sol
L459
Description

The signed message only commits the sender, the contract address, the chain id, the user id, the token, the amount and an expiry. There is no nonce and no record of which signatures have already been used, so the same signature keeps working until it expires. The deposit can therefore be submitted several times with one authorization, and each submission pulls the amount again and writes a new log entry. Cross-chain and cross-contract reuse are prevented because the chain id and the contract address are part of the message, and another party cannot reuse the signature because the depositor address is fixed inside it, which keeps the severity moderate. The realistic impact is repeated charges to a user from a retrying front-end or relayer, and possible mis-crediting if the back-end treats one authorization as one deposit. A proof of concept confirmed that a single signature funded three separate deposits.

Acknowledged

#2 medium Issue
Privileged roles can move all custodied funds without limits
deposit_cashier_v2.sol
L514
Description

The fund distribution and split functions let any address holding access permission transfer arbitrary amounts of any token to arbitrary receivers. The identifier passed in is only used to prevent the same batch from running twice, and it is not connected to any recorded deposit, so there is no link between what was deposited and what can be paid out, and there is no cap. The emergency collectors give the owner the same ability to sweep everything. As a result a single malicious or compromised operator or owner key can empty the entire balance in one transaction, with no timelock, no on-chain multi-signature requirement, and no accounting that limits the amount. Because operator and signer keys are typically online and used often, this is treated as a realistic risk rather than a purely theoretical one. A proof of concept confirmed that an operator could move the full balance to an address of its choosing.

low Issues | 7 findings

Acknowledged

#1 low Issue
Signature recovery is malleable and does not reject the zero address
deposit_cashier_v2.sol
L363
Description

Signatures are recovered with the low-level recovery primitive without normalising the s and v values and without rejecting a zero result. For any valid signature a second, altered but equally valid signature is also accepted, which a proof of concept confirmed by submitting the high-range twin of a valid signature. There is no direct theft today because the deposit path requires a non-zero signer and compares the recovered address against it, but there is an important interaction with the replay finding: if the planned single-use protection records used signature bytes, the malleable twin hashes differently and defeats it. A nonce-based or message-hash-based single-use record avoids this.

Acknowledged

#2 low Issue
Constructor does not validate the signer address
deposit_cashier_v2.sol
L454
Description

The constructor stores the signer address without checking it against the zero address, while the function that updates the signer later does perform that check. If the contract is deployed with a zero signer, deposits revert until the owner corrects it, which leaves the deposit path unusable but does not put funds at risk.

Acknowledged

#3 low Issue
Ownership transfer leaves the previous owner with operator rights
deposit_cashier_v2.sol
L306
Description

Transferring ownership updates the owner but does not revoke the previous owner's access permission and does not grant it to the new owner. Because access permission is what allows distributing funds, splitting funds and pausing deposits, the former owner keeps all of those operator abilities after handing over ownership, which is contrary to the usual expectation of a clean handover. A proof of concept confirmed that the old owner could still pause the contract after transferring ownership.

Acknowledged

#4 low Issue
Unbounded receiver arrays in the distribution loops
deposit_cashier_v2.sol
L525
Description

The receiver loops in the distribution and split functions have no length limit. A very large batch can exceed the block gas limit and become impossible to process in one transaction, and a single bad entry such as a zero address or a zero amount reverts the whole batch. Because only permissioned operators call these functions the issue is largely self-inflicted, but it can still block settlement until the operator splits the work.

Acknowledged

#5 low Issue
Fee-on-transfer and rebasing tokens are mis-accounted on deposit
deposit_cashier_v2.sol
L487
Description

The deposit function records and emits the requested amount, but for tokens that take a fee on transfer or that shrink balances over time the contract actually receives less than that amount. The stored log and the emitted event then overstate what was received, the off-chain ledger over-credits the user, and the real balance is smaller than the sum of the recorded deposits, which can later cause distributions to fall short or revert. A proof of concept with a ten percent fee-on-transfer token showed the contract holding ninety while the log recorded one hundred.

Acknowledged

#6 low Issue
Non-contract token addresses are accepted, allowing phantom deposits
deposit_cashier_v2.sol
L465
Description

The deposit function only rejects the zero address and then relies on the transfer helper, which treats an empty response as success. A low-level call to an address that holds no contract code returns success with no data, so a transfer against an account that is not a token is reported as successful while nothing actually moves. This lets a deposit be recorded and an event emitted with no tokens received, and on the payout paths it silently moves nothing while still consuming the batch identifier. The deposit case requires the signer to have authorised a non-contract token, since the token is part of the signed data, and the payout case requires an operator to pass one, so the practical risk depends on whether the back-end restricts tokens to an approved list. A proof of concept recorded a deposit against an address with no code.

Acknowledged

#7 low Issue
Empty distribution batch permanently consumes an identifier
deposit_cashier_v2.sol
L517
Description

The distribution and split functions accept receiver arrays of equal length, including length zero, and they set the processed flag before the loop runs. With empty arrays the loop body never executes, so the call marks the identifier as processed and emits the event while moving no funds. Since a processed identifier can never be used again, an operator mistake or a malicious operator key can permanently block that identifier from ever being settled, while the off-chain system may believe it was distributed. A proof of concept consumed an identifier with an empty batch and showed that a later real distribution for the same identifier reverts.

optimization Issues | 8 findings

Pending

#1 optimization Issue
Use calldata for external function parameters
deposit_cashier_v2.sol
L459
Description

The deposit, distribution and split functions take their parameters as memory even though they are external entry points. Reading them directly from calldata avoids copying the data into memory and reduces gas, with the largest benefit on the structs that contain the receiver arrays.

Pending

#2 optimization Issue
Replace revert strings with custom errors
deposit_cashier_v2.sol
L1134
Description

The contract and its transfer helper use string reason messages in a large number of require checks. Switching these to custom errors reduces contract size and the gas spent on the failure paths while keeping the same meaning.

Pending

#3 optimization Issue
Cache array length and skip the loop overflow check
deposit_cashier_v2.sol
L525
Description

The distribution and split loops read the array length on every iteration and pay for the built-in overflow check when incrementing the index. Caching the length once and incrementing inside an unchecked block lowers the per-iteration cost.

Pending

#4 optimization Issue
Mark public functions that are never called internally as external
deposit_cashier_v2.sol
L306
Description

Several functions are declared public but are never called from within the contract. Declaring them external is slightly cheaper and communicates intent more clearly.

Pending

#5 optimization Issue
Use type-checked call encoding in the transfer helper
deposit_cashier_v2.sol
L1131
Description

The transfer helper encodes its token calls with a raw selector. Using the type-checked encoding form keeps the same behaviour while letting the compiler verify that the arguments match the intended function signature.

Pending

#6 optimization Issue
Remove unused library code
deposit_cashier_v2.sol
L586
Description

Only one function of the byte-manipulation library is used, and the approval helper in the transfer library is never called. Removing the unused routines reduces deployment cost and the overall surface that must be reasoned about.

Pending

#7 optimization Issue
Build the deposit hash without the empty concat step
deposit_cashier_v2.sol
L469
Description

The deposit function declares an empty bytes value and concatenates the packed parameters onto it before hashing, which is equivalent to packing the values directly. Hashing the packed values directly removes an unnecessary allocation and call on the deposit path.

Pending

#8 optimization Issue
Use prefix increment for the log counter
deposit_cashier_v2.sol
L511
Description

The log counter is incremented with the postfix form. Using the prefix form is marginally cheaper and has the same effect here because the result of the expression is not used.

informational Issues | 8 findings

Pending

#1 informational Issue
Fund distribution functions lack a reentrancy guard
deposit_cashier_v2.sol
L514
Description

The distribution and split functions send tokens in a loop without a reentrancy guard. A token that notifies the receiver during a transfer could re-enter the contract. This is rated informational today because the contract keeps a single pooled balance with no per-deposit accounting, so there is no internal accounting state for a re-entrant call to corrupt, and an operator can already move funds freely, so a callback grants nothing new. It should be promoted in severity once distributions are bound to recorded deposits, because then there is balance state worth protecting.

Pending

#2 informational Issue
Deposit updates state after the external token transfer
deposit_cashier_v2.sol
L487
Description

In the deposit function the token is pulled in before the log entry is written and the counter is incremented. Re-entry into deposit is currently blocked by the reentrancy guard, which a proof of concept confirmed, and there is no accounting state that a re-entrant call could corrupt, so this is a hardening and style item rather than an exploitable issue. The ordering would only become risky if the guard were removed during a future refactor.

Pending

#3 informational Issue
Deposit expiry relies on the block timestamp
deposit_cashier_v2.sol
L467
Description

The deposit deadline is enforced by comparing the supplied expiry against the block timestamp. Validators can nudge the timestamp by a small margin, but this has no practical effect on an expiry check, so the usage is considered safe.

Pending

#4 informational Issue
Event address fields are not indexed
deposit_cashier_v2.sol
L451
Description

The events that record ownership transfer, access permission changes and signer updates carry address fields that are not indexed. Indexing them makes it easier for off-chain systems to filter and track these administrative changes.

Pending

#5 informational Issue
Large amount of unused code increases the review surface
deposit_cashier_v2.sol
L586
Description

Most of the byte-manipulation library is never used, and the approval helper in the transfer library is also unused. The unused storage routines are where the division-before-multiplication pattern appears, and since that code never runs the pattern is harmless, but keeping dead code enlarges the bytecode and the amount that must be reviewed and trusted.

Pending

#6 informational Issue
Deposit hash is built from an uninitialised empty value
deposit_cashier_v2.sol
L469
Description

The deposit function declares an empty bytes value and then concatenates the packed parameters onto it. The empty value defaults to zero length so the result is correct, but the pattern is confusing and unnecessary, and it is the source of the uninitialised local observation. Hashing the packed values directly is clearer.

Pending

#7 informational Issue
Emergency Ether collector is effectively unreachable
deposit_cashier_v2.sol
L327
Description

The contract has no payable entry point, so Ether can only reach it through unusual means. The emergency Ether collector therefore has little practical use in normal operation. It is harmless but misleading to keep without explanation.

Pending

#8 informational Issue
Naming, documentation and duplicated logic
deposit_cashier_v2.sol
L291
Description

State variables and parameters use underscore naming rather than the conventional mixed case, the public and external functions lack documentation comments, the owner check reverts with no reason string, and the two distribution parameter structs are identical while the distribution and split functions are nearly the same. None of these affect security on their own, but together they reduce readability and maintainability and increase the chance of future mistakes.