
Loading...
Loading
Loading...
LoadingLoading audit report...

WalletGuard.ai, powered by Gestalt Labs
Findings selected for deep verification. Where possible we generated a Solidity proof-of-concept and executed it against a forked mainnet.
src/LiquidRon.solFunction: delegateAmountLines: 272-283src/LiquidRon.solFunction: redeem(uint256)Lines: 390The analyzed contract is a DeFi vault implementing ERC-4626 over a Ronin staking protocol, allowing users to deposit WRON, earn staking yield, and withdraw via an epoch-based queue. The analysis identified 0 critical, 4 high, 9 medium, 6 low, and 3 informational findings across 25 deduplicated inputs. The single most dangerous pattern is the inverted boolean logic in the onlyOperator modifier, which simultaneously locks out legitimate operators and permits arbitrary addresses to call privileged staking functions including harvest, delegate, undelegate, and epoch finalization. The contract presents a high overall risk profile and should not be deployed to production without the access control defect and associated operator-permission issues resolved.
Due to a bug in the permission check, any wallet address in the world can call sensitive administrative functions like harvesting rewards, moving staked assets between validators, and finalizing withdrawal epochs. At the same time, the addresses that are supposed to have this permission (designated operators) are actually blocked. This means an attacker could manipulate the vault's staking positions, distort share prices, or interfere with the withdrawal queue with no special access required.
The function that removes inactive validators from the tracked list uses a stale count and a reverse-iteration pattern that breaks when more than one validator is removed in a single call. Because the removal operation rearranges the array, subsequent iterations read the wrong slots, potentially skipping validators or crashing with an out-of-bounds error. If the function consistently reverts, bad validators can never be pruned, which can corrupt staking delegation and share accounting over time.
When users claim their epoch withdrawal, the contract uses a raw token transfer call that does not check whether the transfer succeeded. If the token returns false instead of reverting on failure, the contract marks the withdrawal as completed and then attempts to send native RON from its own balance rather than from the escrow, potentially draining the vault's liquid funds while the escrow retains the user's assets.
2 centralization points identified
Fee withdrawal failure depends on how the operator manages liquid versus staked balances; no attacker-controlled path exists, and the impact is limited to operator UX.
fetchOperatorFee()The unlimited approval to the vault is a design property users should know about; exploitation depends entirely on the vault contract being compromised, not on a standalone attack path.
constructor()Because the onlyOperator modifier is inverted (Finding: 'Inverted operator modifier logic allows non-operators to call restricted functions'), any attacker can call finaliseRonRewardsForEpoch. That function's share-price snapshot occurs after the _withdraw burn rather than before (Finding: 'finaliseRonRewardsForEpoch increments withdrawalEpoch before recording locked price, breaking epoch sequencing'), allowing an attacker to choose a moment where pending rewards are maximally inflated in totalAssets. The attacker finalizes the epoch at a favorable price, then claims via redeem(uint256). The incorrect event emission (Finding: 'redeem(uint256 _epoch) Uses Current Epoch Instead of Requested Epoch in WithdrawalClaimed Event') further obscures the activity in off-chain monitoring. Together these three findings form a complete path: open access to epoch finalization, manipulable price snapshot timing, and event obfuscation.
undelegateAmount in LiquidProxy computes the amount to forward to the vault by summing the operator-supplied _amounts array before verifying what the staking contract actually returned (Finding: 'undelegateAmount in LiquidProxy assumes full undelegate amount is transferred back but does not verify balance'). If the staking contract returns less due to slashing, the call to _depositRONTo will consume pre-existing RON sitting in the proxy from prior harvest calls. The open operator access finding (Finding: 'Inverted operator modifier logic') means an attacker, not just a legitimate operator, can trigger undelegateAmount with crafted amounts to force this imbalance, potentially draining residual proxy balances.
| Agent | Status | Findings | Severity | Confidence | Duration | Coverage |
|---|---|---|---|---|---|---|
| reentrancy | success | 6 | 3M1L | 81% | 1.7m | Cross-function reentrancy in deposit/withdraw/redeem/requestWithdrawal flows, ERC-4626 deposit/withdraw ordering (CEI pattern), Access control modifiers (onlyOperator, onlyVault, onlyOwner), Escrow pattern for deposit flow, LiquidProxy harvest and delegation functions, Withdrawal epoch management and finalisation, Operator fee calculation and withdrawal, ValidatorTracker push/remove logic, Callback patterns in _withdrawRONTo (native RON transfer), Read-only reentrancy via totalAssets view function, Integer overflow/underflow in unchecked blocks, MockRonStaking reward calculation logic |
| access control | failed | 0 | 0% | 45.3s | - | |
| economic | success | 8 | 2H1M2L | 85% | 2.3m | Flash loan attack surface on totalAssets() and share price manipulation, Oracle manipulation - no oracle used, price derived from staking contract state, Access control on all external/public functions, onlyOperator modifier logic correctness, ERC4626 deposit/withdraw/redeem overrides and share math, Harvest sandwiching (deposit before harvest to capture rewards), Withdrawal request and redemption flow correctness, Epoch management and locked price per share calculations, LiquidProxy harvest loop redundancy, pruneValidatorList iteration safety during mutations, Escrow deposit flow and reentrancy in receive(), RonHelper WRON wrapping/unwrapping, ValidatorTracker swap-and-pop correctness, Event emission correctness, CEI pattern compliance in withdrawal flows, Integer overflow/underflow (Solidity 0.8.20 built-in protection), Rounding direction in _convertToAssets, Fee accounting consistency between getTotalRewards() view and post-harvest state |
| logic validation | success | 9 | 2H4M | 86% | 2.0m | Access control modifiers (onlyOperator, onlyVault, onlyOwner), ERC-4626 deposit/withdraw/redeem share price calculations, Withdrawal epoch state machine and request fulfillment, Operator fee accounting and totalAssets() calculation, LiquidProxy harvest loop logic, Array length validation in parallel array functions, ValidatorTracker add/remove logic and pruneValidatorList, Escrow contract and deposit flow, Integer arithmetic in fee calculations, Event emission correctness, Reentrancy in withdraw/redeem flows, RonHelper WRON wrap/unwrap flows, Checks-effects-interactions ordering |
| code quality | success | 12 | 1L | 83% | 2.7m | ERC-4626 compliance (totalAssets, previewRedeem, withdraw, redeem, deposit, mint), ERC-20 compliance (underlying WRON token interactions), Access control (onlyOperator, onlyOwner, onlyVault modifiers), Reentrancy vectors in withdraw/redeem/deposit flows, Withdrawal epoch accounting and finalization logic, Staking proxy harvest and delegation logic, Validator tracker add/remove logic, Escrow deposit flow, Integer overflow/underflow (Solidity 0.8+ checked arithmetic), Unchecked external call return values, Cross-function and cross-contract reentrancy, Gas optimization opportunities, Event parameter correctness, Loop bounds and off-by-one errors, Operator fee calculation |
| compiler bugs | success | 5 | 1M | 89% | 1.7m | Access control modifiers (onlyOperator, onlyVault, onlyOwner), ERC4626 deposit/withdraw/redeem overrides and share calculation, Withdrawal epoch flow: requestWithdrawal, finaliseRonRewardsForEpoch, redeem(epoch), LiquidProxy harvest loop logic and reward claiming, Escrow contract approval and deposit flow, totalAssets() composition including staked and reward amounts, ValidatorTracker add/remove logic and pruneValidatorList iteration safety, RonHelper wrap/unwrap flow for native RON <-> WRON, Event emission correctness, Integer overflow/underflow (Solidity 0.8.20, protected by default), Reentrancy in withdraw/redeem paths (CEI pattern checked), Compiler bug patterns for pragma ^0.8.20 (outside all affected ranges) |
| assembly safety | success | 9 | 2M2L | 85% | 2.1m | Full codepoint-by-codepoint scan for non-ASCII characters (RTLO, zero-width, homoglyphs) in all identifiers, strings, and comments, Absence of inline assembly{} blocks confirmed across all files, Access control modifiers (onlyOperator, onlyVault, onlyOwner), ERC4626 totalAssets() override and share price manipulation, Withdrawal epoch flow: requestWithdrawal, finaliseRonRewardsForEpoch, redeem(epoch), Harvest loop logic in LiquidProxy.harvest(), undelegateAmount balance accounting, ValidatorTracker array mutation safety in pruneValidatorList, Escrow deposit flow and reentrancy in receive(), Operator fee accounting and potential double-counting, Event emission correctness, Integer overflow/underflow (Solidity 0.8.20 safe), Re-entrancy in withdraw/redeem/requestWithdrawal, RonHelper WRON wrap/unwrap logic, MockRonStaking included only for context, not production code |
| l2 specific | success | 10 | 1M1L | 78% | 2.3m | Access control modifiers (onlyOperator, onlyVault, onlyOwner), ERC4626 deposit/withdraw/redeem overrides and share calculation correctness, Reentrancy vectors in requestWithdrawal, withdraw, redeem, deposit, Cross-contract interactions: LiquidRon -> Escrow -> LiquidRon deposit flow, LiquidProxy harvest loop logic and balance-based accounting, Epoch-based withdrawal system: finaliseRonRewardsForEpoch, requestWithdrawal, redeem(epoch), ValidatorTracker add/remove correctness and iteration safety in pruneValidatorList, Operator fee accounting and totalAssets calculation, RonHelper ETH wrapping/unwrapping safety, Integer overflow/underflow (Solidity 0.8.20 safe by default), Event emission correctness, L2-specific patterns (chain ID: 1 = Ethereum mainnet, no L2 markers in code) |
429 {"type":"error","error":{"type":"rate_limit_error","message":"This request would exceed your organization's rate limit of 2,000,000 input tokens per minute (org: 764f99c4-d2f7-40b9-929a-bce5bd69d6b3, model: claude-sonnet-4-6). For details, refer to: https://docs.claude.com/en/api/rate-limits. You can see the response headers for current usage. Please reduce the prompt length or the maximum tokens requested, or try again later. You may also contact sales at https://claude.com/contact-sales to discuss your options for a rate limit increase."},"request_id":"req_011Ca8r4UGRHDHBceJeuAHyP"}
How this affects your report: findings normally surfaced by this specialist are missing; overlapping coverage from other agents still applies.
This automated audit has inherent limitations. The following areas are not covered.
This report is an automated point-in-time assessment and does not guarantee protection against all possible attacks. It does not cover off-chain components, economic modeling, or business logic correctness unless explicitly noted. Changes to the contract after the audit commit are not reviewed. This is not financial or legal advice. WalletGuard, powered by Gestalt Labs, provides this analysis as-is with no warranty of completeness.
[](https://walletguard.ai/audit/45ceee63-0e8d-451b-94ef-e8bf3b7f7a76)
<a href="https://walletguard.ai/audit/45ceee63-0e8d-451b-94ef-e8bf3b7f7a76"> <img src="https://walletguard.ai/api/badge/45ceee63-0e8d-451b-94ef-e8bf3b7f7a76" alt="WalletGuard Audit Badge" /> </a>