GURU.FUNDGo To App

GURU.FUND POST-MORTEM ANNOUNCEMENT

July 28th, 2026

This is Numa, dev and co-founder at Guru.Fund. On July 24, Guru Fund Lotus’ deployments on Ethereum Mainnet were exploited for a value we had last recorded at approximately $101,296.64. Since I am writing this with the intent of explaining precisely what happened, I will share the vulnerable code, the transaction flow, and the operational failure that made the exploit possible. If you are a non-technical reader, feel free to skip any code section.

TLDR: this wasn’t a direct breach of the vault contracts per-se, but rather an unexpectedly enabled contract that could grant arbitrary token allowances from Lotus’ FundVaults.

Lotus architecture

Lotus Vaults hold a fund’s assets: Lotus Controllers implement operations such as deposits, withdrawals and trades. For that to happen, Lotus uses different “adapters” (UniswapV2Adapter, “AerodromeV3Adapter”… etc) to exactly execute the swap instructions necessary for a specific dex.

The controller enforces vaults to execute only code from protocol-authorized “adapter” contracts using delegatecall:

vault.execute(_verifiedAdapter(extCalls[i].adapter), extCalls[i].callData);

Using delegatecall, the vault executes the adapter’s code in the vault’s context. In other words, the vault “borrows” the adapter’s code: all instructions defined in the adapter code are executed in the vault. This is intentional for dex adapters: they encode a token swap flow for a specific dex, approve tokens to a specified router, the router is called to execute the swap, the output is returned to the caller (ultimately, the vault).

The vulnerable legacy adapter

As Gurus with large holdings were starting to take profits, smaller holders of the same tokens were starting to carefully watch their positions. We were then requested a peer-to-peer trading feature, to allow a more private exchange: Gurus could trade their bags directly with users they trusted so to reduce impact on charts.

The vulnerable adapter was originally designed to allow Gurus to trade via our custom P2P contract in a testing environment. This was the vulnerable P2P adapter address:

0x5e0234c98fdcc1c5e69241f6a08c624df6fad91e

It was never verified on Etherscan. Its source code needed to be recovered from Git history and recompiled with the original settings:

  • Solidity 0.8.30
  • IR pipeline enabled
  • optimizer enabled with 200 runs

After recovering its constructor params, the resulting 730-byte runtime matched the deployed bytecode exactly. And this was the complete vulnerable function:

function approveForP2P(
    address p2pContract,
    IERC20 token,
    uint256 amount
) public returns (Trade memory trade) {
    token.forceApprove(p2pContract, amount);
    trade.tokenIn = token;
    trade.tokenOut = token;
    trade.amountIn = 0;
    trade.amountOut = 0;
}

All three security-sensitive values came directly from calldata:

p2pContract // spender
token // vault asset
amount // allowance

Being this adapter originally designed for testing purposes, there was no validation that p2pContract was the real LotusP2P contract, token belonged to an authenticated P2P order, amount matched an authenticated operation, or that the caller was the fund manager.

How did a P2P trade even enter through deposit?

Deposits are designed to accept a list of external calls expected to swap the deposited stablecoin into the portfolio’s underlying assets. The controller only verifies that the adapter address was enabled and authorized at Protocol level. It relies on each adapter to constrain its own calldata safely.

In this case, the attacker submitted a real deposit containing legitimate swaps together with the malicious calls to the legacy P2P adapter. The deposits remained internally consistent: capital entered the vault, portfolio assets were bought, shares were issued.

However, bundled with those operations, the vault also granted unlimited allowances over all its existing holdings to the attacker. Running the vulnerable adapter via delegatecall, the effective operation executed by the FundVault was:

token.approve(attackerContract, type(uint256).max)

Once the deposit had gone through, the attacker could then transfer tokens out of the vault, since it had granted the allowance.

How was this unsafe adapter still enabled?

This is the central operational failure.

The vulnerable adapter was introduced on March 10 and deployed on March 11 for testing purposes. On March 12, it was consciously replaced with a safe version of the P2P adapter.

The replacement fixed all the previously stated safety issues: however, deploying and enabling the replacement did NOT automatically disable the original deployment.

The application, the SDK, even the internal codebase had removed all references to the old address - but on the Protocol registry on-chain storage it was still enabled. The attacker did not need the UI or the SDK: they managed to reconstruct the calldata and directed their transaction to the legacy, forgotten adapter, directly using the unverified selectors embedded in the bytecode.

In other words: although the engineering vulnerability had been addressed in the source code on the newly deployed version, the access control had not been retired on the vulnerable code, and a skillful attacker was able to abuse it.

First exploit transaction

At 10:14:35 UTC, the attacker sent the first attack transaction:

  • Borrowed 470 USDC from Balancer.
  • Called deposit on the Lotus controller.
  • Used legitimate swap calls to satisfy the vault’s deposit composition.
  • Called the legacy adapter twice through the vault.
  • Granted the attacker contract unlimited GURU and WETH allowances.
  • Completed the deposit.
  • Pulled approximately 239,658 GURU and 6.8716 WETH from the vault using transferFrom.
  • Swapped the stolen assets and repaid the flash loan.

The trace contains the exact malicious calls:

approveForP2P(
    0xFF69214681969cc9618cf1C525f9841b471b6785,
    GURU,
    type(uint256).max
);

and:

approveForP2P(
    0xFF69214681969cc9618cf1C525f9841b471b6785,
    WETH,
    type(uint256).max
);

Transaction list

The technique was repeated against all these vaults:

Incident Response

We paused the Ethereum Protocol at 11:40:11 UTC.

Then, disabled the vulnerable adapter at 11:47:35 UTC.

Later we reconstructed the exploit, matched the adapters’ deployed bytecode against their source, inspected their selectors and reviewed existing vault allowances.

The specific exploit path is closed.

One important technical caveat is that disabling an adapter does NOT revoke allowances already written to token contracts. Those allowances remain part of the vault state: assets must NOT be sent back to the affected vaults.

Were the core contracts compromised despite the security audit?

Effectively, the attacker did not:

  • obtain an administrator key;
  • bypass share accounting;
  • upgrade a proxy, since they are not present in the architectural design;
  • forge a manager signature: there is no offchain signature in Lotus, unlike the v0 version;
  • manipulate an oracle: Lotus uses no oracles;
  • directly call an unrestricted vault transfer function

The vault executed instructions as programmed, from a contract that was unexpectedly authorized to do so by the Protocol itself. Perhaps, it could be argued the contract logic was not inherently faulty: in any case, such a configuration failure is still undeniably part of the protocol’s trusted boundaries.

A word on “security through obscurity”

This specific vulnerable adapter was never verified on Etherscan: clearly, that provided no protection.

Its selectors were still recoverable from the bytecode and its behavior could be reconstructed through tracing and decompilation. We had to rebuild the exact source from Git history and reproduce its on-chain bytecode deployment to completely understand what happened.

For every other team tempted to simply “not verify the contracts” as a form of defense: this is a harsh reminder that deployed bytecode isn’t opaque to a capable attacker and that “security through obscurity” doesn’t exist on a public blockchain.

“Is any of this a cover-up?”

I personally want to address speculation that the team carried out the attack. As the technical founder of this project, I am particularly troubled by those accusations: while I cannot dismiss the responsibility for the operational failures under my leadership and for the harm they caused, we certainly did not steal from our users, or orchestrated this attack.

During my entire time with Guru, the team has disclosed to retain high administrative controls, including the disclosed temporary ability to centrally sign fund actions on the v0 release, and with Lotus, pause and unpause the protocol, as well as manage approved adapters. If any of myself or the team members had intended to steal user funds, abusing those controls would have been way simpler than executing a convoluted exploit through a forgotten adapter removed from our own codebase.

Any rough on-chain analysis can also connect the attacker’s activity to addresses previously flagged for phishing and other malicious activity (most notably 0x1e7B...E29A, reported in another hack recently).

Certainly, these labels cant prove identity or attribution, but they provide further clues that are inconsistent with the claim that the Guru team orchestrated this as an inside job.

Our response was not fast enough. We paused the protocol, disabled the vulnerable adapter, reviewed the remaining configuration, published the transactions, and remained publicly available to explain what happened: but it was too late.

While our operational failure made the exploit possible, on behalf of myself and of the entire team I reject any attempt to reframe this as deliberate theft.

Previously Implemented Defenses

Guru.Fund was never operated on the assumption that adversaries would leave it alone. Before this incident, we had already investigated a signature-replay weakness on v0 funds, deployed protections against it, and observed two exploit attempts fail. We did not publicize those unsuccessful attacks at the time because the implemented protections held, no additional user action was required, and we did not want to raise unnecessary alarm. But today users deserve the fullest possible account of the adversarial conditions under which the protocol operated.

The associated on-chain activity is public: address one and address two.

This isn't shared to seek past credit or diminish the losses caused by the exploit, but to convey the unforgiving asymmetry of protocol security. Defenders must identify and close every viable path while an attacker needs to find only one that was missed. We had previously defended Guru successfully, with added safeguards working as intended. This time, a single operational failure was enough to undo years of work and cause real harm to our users.

Post-mortem Operations Shutdown

We are grateful to Decurity.io for reporting this to our attention. The remaining funds are currently contained, and the exploit path described above has been disabled.

Regretfully, we do not have the resources to reimburse the affected users.

Given the exploit, the surrounding market conditions, and the inevitable loss of trust this creates, we do not believe the Guru Fund brand can responsibly continue operating. With a heavy heart, we are proceeding with an orderly shutdown focused on disabling new deposits and allowing withdrawals as safely as possible.

Despite this, our belief in the underlying technology has not disappeared. Myself and others on the team worked on this project for months, at times without compensation, because we genuinely believed this tech can support a legitimate product under more favorable market conditions. These conditions didn’t materialize in time, before this attack happened. If any experienced builder is seriously interested in knowing more about Lotus and collaborating on exploring a future EVM or Solana relaunch with a stronger security and governance model, please get in touch with myself (@numa0x) or @quantummeditate on Telegram

I am publishing this technical account because users and builders deserved more than "an adapter was exploited". They deserved to understand exactly how our system operated, what went wrong, and how that happened.

I renew my sincere apologies to all users affected.

Numa