
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/GaugeController.solFunction: constructorLines: 74-80The analyzed contract suite appears to be a lending rewards and gauge-based governance system, comprising a LendingLedger, GaugeController, VotingEscrow, and LiquidityGauge. The analysis identified 0 critical, 7 high, 9 medium, 4 low, and 4 informational findings across 27 total submissions. The single most dangerous pattern is the combination of missing reentrancy protection in LendingLedger.claim() with unrestricted access to sync_ledger(), which together enable a cross-function reentrancy exploit capable of draining the contract's CANTO reward balance. The contract suite presents substantial security risk and should not be considered safe for production use without remediation of the high-severity findings.
When a user claims their CANTO rewards, the contract sends ETH to the user before all accounting is finalized. If the user is a smart contract, it can exploit this window to call back into the lending system and artificially inflate its own balance, then claim a second time, effectively stealing more rewards than it is owed. Because there is no reentrancy lock on the sync function, this can be chained to drain the entire reward pool.
The contract records when a user last voted, but never actually checks that timestamp before allowing another vote. This means any token holder can vote for the same gauge repeatedly in rapid succession, artificially inflating reward allocations to favored pools and stealing yield from other participants.
The function that records how much a user has deposited in a lending market has no meaningful access control. Any attacker who can route a call through the liquidity gauge mapping can arbitrarily set any user's balance up or down, enabling theft of CANTO rewards or denial of legitimate claims without making any real deposit.
2 centralization points identified
The risk arises from the governance key management process and admin behavior rather than a permissionless exploit path; no external attacker can trigger this without first compromising the governance key.
setGovernance()The function is governance-controlled and the DoS affects only the governance transaction itself rather than user funds; this is an operational footgun for the protocol operator rather than an externally triggerable exploit.
setRewards()An attacker deploys a contract that is either a whitelisted lending market or controls one. Step 1: the attacker accumulates a small legitimate balance via sync_ledger. Step 2: the attacker calls claim(), which sends ETH via a low-level call before any reentrancy guard stops further calls. Step 3: in the receive() callback, the attacker calls back into sync_ledger (which has no reentrancy guard) to inflate their user.amount for the same market. Step 4: the attacker re-enters claim() from within the same callback, and because user.amount is now higher and rewardDebt has not been updated for the new inflated amount, additional CANTO is released. This chain exploits the missing nonReentrant on both claim() and sync_ledger() and the lack of caller validation on sync_ledger(), allowing complete drainage of the LendingLedger CANTO balance.
An attacker with any VotingEscrow balance exploits the missing cooldown in vote_for_gauge_weights to repeatedly vote for a controlled gauge in the same block, artificially inflating its relative weight. Because update_market uses the current epochTime for all historical epochs rather than epoch-specific timestamps, inflated gauge weights are retroactively applied to past reward periods as well. The attacker then calls claim() to collect rewards calculated using the manipulated weight, stealing yield that should have gone to other market participants.
A user who has accrued a high rewardDebt (meaning they have already been credited for rewards they have not yet claimed) can transfer their LiquidityGauge tokens to a fresh address with zero rewardDebt. The _afterTokenTransfer hook calls sync_ledger, updating the new address's balance as if it had always held those tokens, while the new address has no prior rewardDebt. The new address can then call claim() and receive rewards that were already logically assigned to the original holder, effectively double-claiming accumulated yield.
| Agent | Status | Findings | Severity | Confidence | Duration | Coverage |
|---|---|---|---|---|---|---|
| reentrancy | success | 7 | 2H3M | 79% | 1.3m | Cross-function reentrancy in LendingLedger (claim + sync_ledger), CEI pattern compliance in VotingEscrow.withdraw(), ERC-777 callback patterns in token transfers, LiquidityGauge._afterTokenTransfer callback chain, ReentrancyGuard usage across contracts, Vote power accounting logic in GaugeController, Gauge removal and user power cleanup, Market whitelisting and gauge lifecycle management, Epoch time calculation correctness in update_market, ETH transfer patterns in VotingEscrow and LendingLedger, Access control on governance functions, Integer overflow/underflow in reward calculations |
| access control | success | 8 | 1H2M1L | 77% | 1.3m | Access control on all governance functions (onlyGovernance modifier), Single-step vs two-step ownership transfer patterns, Reentrancy in claim() and sync_ledger() with ETH transfers, Authentication in sync_ledger - caller validation, Signature verification - none present, N/A, Initializer protection - no upgradeable contracts, N/A, delegatecall usage - none present, tx.origin authorization - none present, ecrecover usage - none present, Gauge weight manipulation and vote_for_gauge_weights cooldown, Integer overflow/underflow in reward calculations, LiquidityGauge _afterTokenTransfer callback and sync_ledger interaction, VotingEscrow lock management and delegation logic, GaugeController type/weight management, LendingLedger whitelist enforcement and market de-listing, Flash loan governance attack vectors - VotingEscrow uses lock-based voting, not spot balance, Cross-function reentrancy between claim, sync_ledger, and LiquidityGauge transfers, ERC20 standard compliance in LiquidityGauge, Epoch time calculation correctness in update_market |
| economic | failed | 0 | 0% | 1.9m | - | |
| logic validation | success | 10 | 3H2M2L | 79% | 1.6m | Input validation and parameter bounds in all public/external functions, Arithmetic safety including uint128 truncation in accumulators, State machine integrity in gauge addition/removal lifecycle, Reentrancy vectors in VotingEscrow.withdraw and LendingLedger.claim, Access control on governance functions, Logic correctness of epochTime calculation in update_market, vote_for_gauge_weights cooldown enforcement, remove_gauge type underflow when gauge_types_ is zeroed before _remove_gauge_weight, sync_ledger caller authorization, LiquidityGauge token transfer reward loss, setRewards unbounded loop, whiteListLendingMarket logic ordering, EIP-712/encoding hash collision (not applicable - no signatures used), Timestamp dependence in VotingEscrow and GaugeController |
| code quality | success | 11 | 81% | 2.0m | Reentrancy patterns in LendingLedger and VotingEscrow, Access control on governance functions, Integer overflow/underflow and unsafe downcasts, ERC-20 compliance in VotingEscrow, Voting logic correctness in GaugeController, Epoch time calculation accuracy in LendingLedger, Gauge removal and weight correction logic, LiquidityGauge token transfer hooks and sync_ledger interactions, Loop bounds and off-by-one errors, Flash loan attack vectors, Gas optimization opportunities, State machine correctness for lock management, Delegation logic in VotingEscrow | |
| compiler bugs | success | 5 | 1M | 80% | 1.1m | VotingEscrow: reentrancy in withdraw(), createLock(), increaseAmount() - protected by nonReentrant, VotingEscrow: checkpoint logic and slope change accounting, VotingEscrow: delegation mechanics and reward debt, GaugeController: vote_for_gauge_weights cooldown enforcement, GaugeController: gauge type and weight arithmetic for overflow/underflow, GaugeController: gauge removal slope accounting, LendingLedger: claim() reentrancy via native token transfer, LendingLedger: sync_ledger access control and delta accounting, LendingLedger: update_market epochTime calculation correctness, LendingLedger: reward distribution math and precision, LiquidityGauge: ERC20 transfer hook calling sync_ledger for all transfers, LiquidityGauge: deposit/withdraw flow and underlying token handling, Compiler bug analysis: pragma ^0.8.16 - outside all historical compiler bug affected ranges, Integer arithmetic: all critical paths checked for overflow/underflow under 0.8.x |
| assembly safety | success | 10 | 2M1L | 83% | 1.7m | Full codepoint-by-codepoint scan for non-ASCII characters in all identifiers, strings, and comments across all files - none found, Scan for assembly{} blocks - none present in any file, Reentrancy vulnerabilities in LendingLedger.claim() and sync_ledger(), Access control on governance functions in GaugeController and LendingLedger, sync_ledger access control and who can call it, vote_for_gauge_weights logic: cooldown enforcement, power accounting, gauge type resolution, remove_gauge integer underflow risks in _remove_gauge_weight, gauge_type=-1 corruption when operating on removed gauges, LiquidityGauge _afterTokenTransfer double-sync and ordering issues, epochTime computation error in update_market, Integer overflow/underflow risks throughout (Solidity 0.8+ checked arithmetic), ERC20 standard compliance in LiquidityGauge, VotingEscrow withdraw() reentrancy (protected by nonReentrant), VotingEscrow createLock/increaseAmount arithmetic, GaugeController _get_total, _get_sum, _get_weight loop bounds (max 500 iterations), Homoglyph and RTLO attacks in all variable/function names, Keyword obfuscation for assembly/selfdestruct/delegatecall identifiers |
| l2 specific | success | 11 | 2M1L | 71% | 2.1m | VotingEscrow: lock creation, withdrawal, delegation, checkpoint math, reentrancy guards, GaugeController: gauge addition/removal, vote_for_gauge_weights cooldown, weight calculations, type management, LendingLedger: sync_ledger access control, claim reentrancy, update_market epoch time calculation, accCantoPerShare overflow, LiquidityGauge: depositUnderlying/withdrawUnderlying, _afterTokenTransfer interactions, ERC20Burnable exposure, Cross-contract interactions: sync_ledger authentication flow, gauge weight write during reward update, Integer arithmetic: int128/uint128 casts, underflow/overflow in weight calculations, Access control: governance management, whitelisting flows, L2-specific patterns: contract deployed on mainnet (Chain ID 1), no L2 imports or predeploy addresses detected |
Invalid JSON in Claude response (stop_reason: end_turn, outputTokens: 7113)
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/714224ad-00c7-4b07-8a98-17855b6e6334)
<a href="https://walletguard.ai/audit/714224ad-00c7-4b07-8a98-17855b6e6334"> <img src="https://walletguard.ai/api/badge/714224ad-00c7-4b07-8a98-17855b6e6334" alt="WalletGuard Audit Badge" /> </a>