On Polygon block 87132217 at 2026-05-19T20:04:48Z, an attacker manipulated the EFI/DAI Uniswap V2 pair and used the inflated spot price inside ElevateFi’s staking vault. The vault priced fixed-USD staking packages directly from the pair’s current reserves, so the attacker could make each $25,000 package require only 7.135164705123996531 EFI. The attacker repeated the package-7 stake one hundred times, creating $2,500,000 of recorded staking principal for only 713.5164705123996531 EFI of principal.

Thirty-four blocks later, the same attacker account claimed staking rewards from the vault. The claim converted 25000e18 USD-denominated rewards back into EFI at the then-current EFI/DAI spot price, and the vault transferred 6256.533425502594495059 EFI to the attacker. The confirmed realized extraction from the follow-up claim was approximately $25,000 in EFI.

Root Cause

Vulnerable Contract

ElevateFi Staking Vault proxy 0x816ec92012e61269dcfe72188fe6d2352defce74

  • Proxy: yes
  • Implementation reached by the staking calls: 0xcddc83a34fc9a7b9e6b1df7c14b585cf73283174
  • EFI token: 0xae840deab9916d80fadf42e218119a6051468169
  • DAI token on Polygon: 0x8f3cf7ad23cd3cadbd9735aff958023239c6a063
  • EFI/DAI price pair: 0xaec86dc2a08cd7cf8d90ee71d0e4864f25ba497b
  • Source type: verified Solidity source

Vulnerable Function

Primary vulnerable entry point:

  • stakeEFI(uint8) (0x56214ed4) on the staking vault proxy, executed in the implementation by delegatecall.

Supporting functions that complete the exploit path:

  • getPriceUSD() reads the EFI/DAI pair reserves and returns the spot DAI-per-EFI price.
  • _createV4StakeEntry(address,uint256,uint256,uint32,uint8,bool) stores the fixed USD package value as long-lived staking principal.
  • claimStakeRewards(uint256) (0xdd69becd) later converts USD-denominated rewards back into EFI with another spot-reserve read.

Vulnerable Code

The implementation treats the current EFI/DAI pair reserves as an oracle.

function getPriceUSD() public view returns (uint256) {
    (uint112 r0, uint112 r1,) = pricePair.getReserves(); // <-- VULNERABILITY: raw AMM spot reserves are used as the price oracle
    (uint256 resE, uint256 resD) = pricePair.token0()==address(efi)
        ? (r0, r1) : (r1, r0);
    if(resE == 0 || resD == 0){
        return 0;
    }
    return resD * 1e18 / resE; // <-- VULNERABILITY: no TWAP, delay, liquidity sanity check, or manipulation guard
}

stakeEFI(uint8) consumes that spot price in the same transaction and converts a fixed USD package into an EFI amount.

function stakeEFI(uint8 packageId) external {
    require(v4FinalInitialized, "v4 not initialized");
    require(msg.sender == tx.origin, "only EOA");
    require(!walletFrozen[msg.sender], "Wallet frozen");
    require(packageId < 8, "bad package");

    require(userStakedUsd[msg.sender] != 0 || stakeBalance[msg.sender] == 0, "init required");

    uint256 packageUsd = simplePackageUsd[packageId];
    uint32 ratePpm = simplePackageRatePpm[packageId];

    require(packageUsd > 0, "package not set");
    require(ratePpm > 0, "rate not set");

    rebase();

    uint256 priceUSD = getPriceUSD(); // <-- VULNERABILITY: attacker-controlled same-transaction spot price is trusted
    require(priceUSD > 0, "bad price");

    _bankStakingReward(msg.sender);

    uint256 efiAmount = _usdToEfiCeil(packageUsd, priceUSD); // <-- VULNERABILITY: inflated price makes the required EFI amount artificially small
    require(efiAmount > 0, "zero EFI");

    efi.burnFromStakingContract(msg.sender, efiAmount);
    efi.mint(address(this), efiAmount);

    _createV4StakeEntry(
        msg.sender,
        efiAmount,
        packageUsd,
        ratePpm,
        packageId,
        true
    ); // <-- VULNERABILITY: the full fixed USD package is booked from the manipulated quote
}

The persistent accounting is updated in USD terms, not in the actual market value of the EFI principal deposited under normal conditions.

function _createV4StakeEntry(
    address user,
    uint256 efiAmount,
    uint256 stakeUsd,
    uint32 ratePpm,
    uint8 eventPackageId,
    bool updateTeamBusiness
) internal {
    require(user != address(0), "zero user");
    require(efiAmount > 0, "zero EFI");
    require(stakeUsd > 0, "zero USD");
    require(ratePpm > 0, "zero rate");

    userStakedUsd[user] += stakeUsd; // <-- VULNERABILITY: manipulated USD notional becomes durable principal
    totalActiveStakedUsd += stakeUsd;
    activeRewardUsdRate[user] += stakeUsd * uint256(ratePpm); // <-- VULNERABILITY: rewards accrue from spoofed USD principal

    simpleStakeEntries[user].push(
        SimpleStakeEntry({
            amount: efiAmount,
            stakeUsd: stakeUsd,
            matureAtUsd: userStakedUsd[user] * 2,
            ratePpm: ratePpm
        })
    );
}

The claim path later converts USD rewards back into EFI with a fresh spot-reserve read.

function claimStakeRewards(uint256 usdAmount)
    external
    returns (uint256 paidEfi, uint256 burnedEfi)
{
    require(v4FinalInitialized, "v4 not initialized");
    require(msg.sender == tx.origin, "only EOA");
    require(!walletFrozen[msg.sender], "Wallet frozen");
    require(userStakedUsd[msg.sender] > 0, "no stake cap");

    rebase();
    _bankStakingReward(msg.sender);

    uint256 availableUsd = unclaimedStakingRewardUsd[msg.sender];
    require(usdAmount > 0 && usdAmount <= availableUsd, "Amount too large");

    unclaimedStakingRewardUsd[msg.sender] = availableUsd - usdAmount;

    uint256 priceUSD = getPriceUSD(); // <-- VULNERABILITY: payout conversion still depends on current AMM spot reserves
    require(priceUSD > 0, "bad price");

    (paidEfi, burnedEfi, ) = _settleRewardUsd(msg.sender, usdAmount, priceUSD);

    _processMaturedStakeEntries(msg.sender, priceUSD);

    emit PackageRewardClaimed(msg.sender, 0, paidEfi, epochNumber);
}

Why It’s Vulnerable

Expected behavior: a staking vault that accepts fixed USD packages should use a manipulation-resistant price source before deciding how much EFI is required. At minimum, the price used to create long-lived staking principal should not be a same-transaction AMM spot quote that the caller can move immediately before staking.

Actual behavior: getPriceUSD() reads pricePair.getReserves() and stakeEFI(uint8) immediately trusts that value. A caller can buy EFI from the EFI/DAI pair, temporarily inflate the DAI-per-EFI reserve ratio, and make _usdToEfiCeil(packageUsd, priceUSD) return a tiny EFI amount for a large package. _createV4StakeEntry(...) then persists the full packageUsd into userStakedUsd and activeRewardUsdRate, converting a transient reserve distortion into durable reward accounting.

Normal flow: a user stakes package 7 when the EFI/DAI price is near the normal market price, so the vault requires thousands of EFI to represent a $25,000 package. Attack flow: the attacker first distorts the EFI/DAI reserves, makes the vault believe one EFI is worth thousands of DAI, and records the same $25,000 package while depositing only 7.135164705123996531 EFI per stake.

The second half of the exploit is a denomination mismatch over time. The manipulated stake transaction stores USD notional, while the later claim transaction converts USD rewards back into EFI at the then-current pair price. When the spot price normalizes, the attacker receives far more EFI than the manipulated staking deposits should have entitled.

Primary classification: price_manipulation

Secondary classification: flash_loan_abuse

Attack Execution

High-Level Flow

  1. The attacker invoked a helper path that sourced temporary DAI liquidity.
  2. The attacker used that DAI to buy EFI from the EFI/DAI pair and heavily distort the spot reserves.
  3. While the manipulated price was live, the attacker approved the staking vault to spend EFI.
  4. The attacker called the fixed-USD staking package function one hundred times using package 7.
  5. Each stake deposited only a small amount of EFI but credited the attacker with a full $25,000 staking package.
  6. The temporary liquidity route was unwound, leaving the false staking principal recorded in the vault.
  7. Thirty-four blocks later, the same attacker account claimed USD-denominated staking rewards and received EFI from the vault.

Price Manipulation and Stake Accounting

The first relevant EFI/DAI reserve read showed approximately 42,172.012884861914018312 DAI and 13,110.999083987396476745 EFI, implying a spot price of about 3.216536940832147930645553276 DAI/EFI because DAI is token0 in the pair.

The attacker then transferred 1,351,725.088709128236763437 DAI into the EFI/DAI pair. The pair transferred 12,713.17166791267343003 EFI back to the attacker, leaving approximately 1,393,897.101593990150781749 DAI and 397.827416074723046715 EFI in reserves. That reserve state implied a manipulated spot price of about 3503.773358174433937102284438 DAI/EFI.

At that manipulated price, package 7 (25000e18 USD) required only 7.135164705123996531 EFI. The attacker repeated the package-7 stake one hundred times, so the vault consumed 713.5164705123996531 EFI in total while recording $2,500,000 of staking principal.

The execution trace shows exactly one hundred calls from the attacker into the staking vault with selector 0x56214ed4, and each proxy-level call is paired with a delegatecall into implementation 0xcddc83a34fc9a7b9e6b1df7c14b585cf73283174. During that staking loop, the vault repeatedly read the same EFI/DAI pair reserves through getReserves().

Claim Transaction

The follow-up claim transaction 0x3b366a43a2611e4e017392335064ef8423ea1e5b9ac0c630a1032a23bd938f3c occurred at Polygon block 87132251, thirty-four blocks after the staking transaction. It was sent by the same attacker smart account, 0x7abd3f84e28f49f8f3d64fa21981fa36e4fb37f0.

The claim path first queried viewStakeRewards(address) and then called claimStakeRewards(uint256) with 25000e18. During the claim, the vault again read the EFI/DAI pair reserves and converted the USD reward amount into EFI. The EFI token emitted a Transfer from the staking vault to the attacker for raw amount 6256533425502594495059, equal to 6256.533425502594495059 EFI.

Detailed Call Trace

Exploit transaction 0x2bd7213a764dd93d18dedeca7f4e0cf5c3cdce1739d79b53e41b72ec9efed87e:

Attacker smart account 0x7abd3f84e28f49f8f3d64fa21981fa36e4fb37f0
-> attacker smart account
   CALL flash(uint256,uint256) (0x828cc5ce)

Temporary liquidity and manipulation leg
-> flash-loan and callback route supplies DAI to the attacker-controlled execution path
-> DAI token transfers 1,351,725.088709128236763437 DAI from the attacker to the EFI/DAI pair
-> EFI/DAI pair transfers 12,713.17166791267343003 EFI to the attacker

Staking loop, repeated 100 times
-> staking vault proxy 0x816ec92012e61269dcfe72188fe6d2352defce74
   CALL stakeEFI(uint8) (0x56214ed4), packageId = 7
   -> implementation 0xcddc83a34fc9a7b9e6b1df7c14b585cf73283174
      DELEGATECALL stakeEFI(uint8) (0x56214ed4)
      -> EFI/DAI pair 0xaec86dc2a08cd7cf8d90ee71d0e4864f25ba497b
         STATICCALL getReserves() (0x0902f1ac)
      -> EFI token burn path
         7.135164705123996531 EFI burned from the attacker per stake
      -> EFI token mint path
         7.135164705123996531 EFI minted to the vault per stake

Claim transaction 0x3b366a43a2611e4e017392335064ef8423ea1e5b9ac0c630a1032a23bd938f3c:

Attacker smart account 0x7abd3f84e28f49f8f3d64fa21981fa36e4fb37f0
-> attacker smart account
   CALL 0x4e71d92d

Reward query and claim
-> staking vault proxy 0x816ec92012e61269dcfe72188fe6d2352defce74
   STATICCALL viewStakeRewards(address) (0xb28775b8)
-> staking vault proxy 0x816ec92012e61269dcfe72188fe6d2352defce74
   CALL claimStakeRewards(uint256) (0xdd69becd), amount = 25000e18
   -> EFI/DAI pair 0xaec86dc2a08cd7cf8d90ee71d0e4864f25ba497b
      STATICCALL getReserves() (0x0902f1ac)
   -> EFI token 0xae840deab9916d80fadf42e218119a6051468169
      Transfer 6256.533425502594495059 EFI from the vault to the attacker

Financial Impact

The confirmed realized extraction is 6256.533425502594495059 EFI, corresponding to approximately $25,000 for the 25000e18 USD reward claim. The claim-time conversion implies a price of about 3.9958 DAI/EFI for that payout.

The exploit transaction itself created the false staking position rather than realizing the final payout. In that transaction, the staking vault received 713.5164705123996531 EFI from the one hundred package-7 stakes, while the attacker’s temporary DAI manipulation route ended with a DAI cost. The economic payoff occurred in the later claim transaction, where the vault transferred 6256.533425502594495059 EFI to the attacker.

The accounting impact is larger than the single observed claim. The one hundred manipulated stakeEFI(7) calls created $2,500,000 of fake staking principal and therefore a much larger reward and payout-cap surface than the real EFI principal would justify. Based only on confirmed on-chain follow-up, the realized loss is the 6256.533425502594495059 EFI claim, and the $2,500,000 recorded principal is the latent accounting exposure created by the exploit path.

Evidence

  • Exploit transaction: 0x2bd7213a764dd93d18dedeca7f4e0cf5c3cdce1739d79b53e41b72ec9efed87e
  • Claim transaction: 0x3b366a43a2611e4e017392335064ef8423ea1e5b9ac0c630a1032a23bd938f3c
  • Chain: Polygon
  • Exploit block: 87132217, timestamp 2026-05-19T20:04:48Z
  • Claim block: 87132251
  • Attacker smart account: 0x7abd3f84e28f49f8f3d64fa21981fa36e4fb37f0
  • Vulnerable staking vault proxy: 0x816ec92012e61269dcfe72188fe6d2352defce74
  • Vulnerable implementation reached by delegatecall: 0xcddc83a34fc9a7b9e6b1df7c14b585cf73283174
  • EFI token: 0xae840deab9916d80fadf42e218119a6051468169
  • DAI token: 0x8f3cf7ad23cd3cadbd9735aff958023239c6a063
  • EFI/DAI pair: 0xaec86dc2a08cd7cf8d90ee71d0e4864f25ba497b
  • Selector check: stakeEFI(uint8) = 0x56214ed4
  • Selector check: viewStakeRewards(address) = 0xb28775b8
  • Selector check: claimStakeRewards(uint256) = 0xdd69becd
  • Selector check: getReserves() = 0x0902f1ac
  • Exploit transfer: 1,351,725.088709128236763437 DAI from the attacker to the EFI/DAI pair
  • Exploit transfer: 12,713.17166791267343003 EFI from the EFI/DAI pair to the attacker
  • Staking loop: 100 package-7 staking calls, each consuming 7.135164705123996531 EFI
  • Total manipulated staking principal consumed: 713.5164705123996531 EFI
  • Total manipulated staking principal recorded: $2,500,000
  • Claim transfer: raw amount 6256533425502594495059 EFI from the staking vault to the attacker, equal to 6256.533425502594495059 EFI