Verdex Audit #2
We went through every Verdex smart contract, our server and the web app, line by line, and ran static analysis over all of it. Then we fixed what we found, redeployed the contracts and published the source. This page lists every finding and what happened to it.
This is an internal audit, not a third-party one. It was done by the Verdex team, like our first audit in October. No third-party firm has audited the contracts yet, and you should treat them that way.
Summary
We found no way for an outside attacker to take user funds. The serious findings came down to three things: how much users had to trust the Verdex executor that runs automated trades, how much power the Lend owner had, and a set of Base contracts that could not execute at all.
All of them are now fixed. We corrected the source, tested it (66 tests, many on forks of Robinhood Chain and Base), had the fixes reviewed again, and deployed 16 new contracts on 8 November. Every one is verified on Sourcify with an exact match. No one had used the old contracts, so nobody needed to move anything.
| Severity | Found | Fixed | Mitigated | Acknowledged |
|---|---|---|---|---|
| Critical | 1 | 1 | 0 | 0 |
| High | 6 | 6 | 0 | 0 |
| Medium | 9 | 8 | 1 | 0 |
| Low | 20 | 16 | 1 | 3 |
| Informational | 4 | 0 | 1 | 3 |
The new contracts
These are the contracts the app uses from today. Click an address to read its verified source.
| Contract | Chain | Address |
|---|---|---|
| Treasury | Robinhood Chain | 0x6dd1…2933 |
| Lend | Robinhood Chain | 0x14b4…5a10 |
| Leverage | Robinhood Chain | 0x0f5c…a4eb |
| Orders | Robinhood Chain | 0xa852…d71e |
| Vaults | Robinhood Chain | 0x41fd…059d |
| Auto-Invest | Robinhood Chain | 0x4d88…9bac |
| Agent | Robinhood Chain | 0xe814…e99f |
| Index router | Robinhood Chain | 0x7f30…ee3e |
| Index plans | Robinhood Chain | 0xf9f6…fd75 |
| Term loans | Robinhood Chain | 0x82a4…2d7b |
| Mines | Robinhood Chain | 0x5974…9b99 |
| Gift | Robinhood Chain | 0xb60c…4b40 |
| Orders | Base | 0x6dd1…2933 |
| Vaults | Base | 0x14b4…5a10 |
| Auto-Invest | Base | 0x9675…f73a |
| Index router | Base | 0x1294…6060 |
Options and existing index tokens were not affected and keep their addresses. The old contracts stay on-chain but the app no longer uses them.
What changed for users
- Automated trades (orders, vaults, auto-invest, agent, index plans) only run while a stock's pool is within about 2% of its 30-minute average price. In a sharp move, a fill can wait until the average catches up. You can always run your own order or vault yourself at the current price.
- Approvals in the app are now for the exact amount of each swap, and only to LI.FI's own contract.
- Mines pays at most 2x per roll (it was 4x), and missed rolls catch up at most once every 20 hours. The new Mines contract starts its roll count again from roll 1.
- Treasury payouts are announced on-chain 48 hours before they can be paid.
- Mines rent is not refunded, and no contract has a third-party audit yet.
Contracts
Base automation contracts could not execute
FixedThese contracts read the Aerodrome Slipstream pool price with a 7-field layout, but Slipstream returns 6. Every price read reverted, so nothing on Base could ever fill. No one had used them and no funds were held.
Corrected and redeployed. Orders, vaults and plans were tested end to end on a fork of Base.
Executor-run trades used only the pool price at that moment
FixedThe minimum a trade had to return came from the pool price in the same transaction, so an executor that moved the price first could fill at a bad price. For the agent, a sale could be valued at a crashed price.
Every execution now checks the pool's 30-minute average price and refuses to run if the current price is more than about 2% (200 ticks) away. Sales are valued at the higher of the two prices. What an executor can still do is bounded by that 2% band plus the slippage you set.
The Lend owner could take funds through the reserve function
FixedReserves were paid out without checking the market held that cash, and interest rates had no upper limit.
A payout is now capped at the market's own cash, and rates are capped at 20% base plus 300% slope. All 14 markets were recreated with their previous settings.
Lend liquidations could write off debt that the collateral still covered
FixedWith some settings, a liquidator who moved the pool price could seize all the collateral and leave part of the debt as a loss for suppliers.
Debt is only written off when the position is underwater at the 30-minute average price, and market settings that allow the problem are now rejected.
Treasury payouts were sent wherever the executor chose
FixedHalf of every VERDEX buy is burned in the same transaction, as described. The other half was paid by an executor to any list it chose, at once.
A payout list is now published on-chain and can only be paid 48 hours later, exactly as published. The owner can cancel a pending list (and so can delay payouts) but cannot take the funds.
Mines roll weights could be pushed in thin pools
MitigatedA roll's weight comes from a 30-minute average price move. Holding a pool's price up for those 30 minutes could raise a roll's payout.
The cap is now 2x instead of 4x, so the most such a push can add is far smaller. It can still pay when a mine's reserve is large compared with its pool.
Index plan tips were paid whatever the size of the buy
FixedThe tip is now paid in proportion to what was actually bought; the rest goes back to the plan's owner.
A Lend market's price could be made unavailable
FixedMarkets now require the pool to store enough price history for their averaging window, both when created and when changed.
Partial fills could leave tokens in the contract
FixedEvery swap in Orders, Vaults, Auto-Invest, the agent and the index router now reverts unless the whole input is used.
Smaller contract issues
- Fixed Mines: missed rolls catch up at most once every 20 hours.
- Fixed Term loans: 0% offers can be taken; each loan is at least 1 USDG unless it takes the whole remainder.
- Fixed Lend: a market wiped out by bad debt can take new supply again; liquidations and borrows that would move zero shares revert; settings changes emit events.
- Fixed Leverage: shorts are capped at 2x in the contract, as longs already were.
- Fixed Index: redemptions pay pro rata when a component runs short, and new shares cannot be issued until it is fully backed again. The router's sells leave nothing behind.
- Fixed Treasury: the USDG leg of its floor uses the better of the current and average price; only listed spenders can be approved; stray tokens can only be sent to the burn address; ownership changes take two steps.
- Fixed Gift: a gift key is scoped to its sender, so copying one cannot block a gift.
- Fixed Vote: closed rounds are weighed at the balances held when they closed.
- Acknowledged Holding any amount of VERDEX waives the Leverage and index fees. This is the product rule across Verdex.
- Acknowledged Anyone can temporarily fill a Lend or Leverage cap with their own funds.
- Acknowledged Changing Lend's reserve share does not settle interest first, so it applies to interest not yet accrued.
Notes
- Checked A Mines pool with short price history could have stopped all rolls. All seven pools store 7,200 to 14,400 price points, and the new contract refuses any pool with too few.
- Acknowledged Options: the writer picks the underlying, and a token with transfer restrictions could block exercise.
- Acknowledged Feed: a single poster sets the price. No Verdex contract reads it and no feed is deployed today.
- Acknowledged Per-wallet lists of options, loans and offers are never pruned.
Server and web app
One server key held too many roles
FixedA single key ran every executor, the Mines keeper, the gift relay and the treasury executor, and its storage needed hardening.
Replaced with new keys, one for automation and one for the treasury, held only in our host's secret store and never in uploaded files. The new contracts trust only the new keys, and the old key has no role in them.
The automatic gas top-up signed whatever route the aggregator returned
FixedIt now signs only routes to LI.FI's own contract, with no ETH attached.
Spam plans could waste executor gas
FixedThe executor skips anything with a tip under 0.01 USDG, runs at most five items per owner and sixty per pass, and caps the gas of every transaction.
A buy link could show a fake token name and amount
FixedA ?buy= link now takes the token's name and decimals from the token list or the quote, never from the link, and warns, showing the address, when a token is not listed.
Notifications could be pointed at any web address
FixedOnly real browser push services are accepted, every send times out after 10 seconds, sign-ups are rate limited, and the push passes cannot overlap.
Unlimited approvals to the swap aggregator
FixedApprovals are now for the exact amount of each swap, and the app refuses any route that does not go through LI.FI's own contract.
Chart cache could grow without limit
FixedChart requests are normalised and capped, and the data-provider queue is bounded.
Smaller server issues
- Fixed API errors no longer include internal details.
- Fixed The static file check uses the full path.
- Fixed Rate limits use the address our host's edge saw, not one the client can write.
- Fixed The quote API rejects tokens it does not know instead of guessing their decimals.
- Mitigated Referrer-Policy, HSTS, Permissions-Policy and a base CSP are set. A full script CSP is not yet, because the app is designed to be embedded by partners.
Who can do what now
No admin at all
Mines, Term loans, Options and Gift have no owner and no rescue function. Nobody can change them or move funds out of them.
Executor list only
Orders, Vaults, Agent, Auto-Invest and Index plans: the admin can only choose executors, and executors are bound by each user's limits and the 2% price band. Today the only executor is Verdex's server.
Owner settings
Lend: market parameters within hard bounds, and capped reserves. Treasury: executors, routers, spenders, burn share and slippage band, plus cancelling a pending payout. Index router and Leverage: a fee of at most 1%, plus caps.
The VERDEX liquidity position is held by a launch locker with no withdraw function, so it is locked for good. The team holds no VERDEX.
How we reviewed
- Read every line of all 20 contracts in five groups, tracing each finding to a concrete scenario. Findings without one were dropped.
- Ran Slither 0.11.6 on every contract. Its high-severity flags were patterns we checked by hand: executors pulling from users who approved them, and balance checks around transfers.
- Checked claims on-chain: Base pool price layout, Mines pool price history, and contract balances and usage.
- Wrote 66 tests for the fixes, many on forks of Robinhood Chain and Base with real pools and tokens, then had the fixed code reviewed again and fixed what that second pass found.
- Rehearsed the full deployment on forks before deploying, and verified every new contract on Sourcify.
Next
- An independent audit before any cap is raised.
- A full script CSP once partner embeds move to a dedicated route.
Found something we missed? Our bug bounty is described at useverdex.xyz/docs#bug-bounty. Our first internal audit, from October, is at useverdex.xyz/docs#audit. This page describes our own review of our own code. It is not an audit, a guarantee or financial advice.