Convergence Finance CvxRewardDistributor claimMultipleStaking() Fake-Staking-Contract Reward Mint (2024-08-01)

  • Name: Convergence Finance CvxRewardDistributor claimMultipleStaking() fake-staking-contract reward mint
  • Date: 2024-08-01
  • Protocol: Convergence Finance
  • Chain: Ethereum
  • Category: account confusion / fake integration endpoint / reward-source substitution / unbounded reward mint
  • Loss: about $210k-$212k of realized dump value plus roughly $2k of stolen unclaimed Convex rewards; roughly 58M CVG minted and dumped, plus a much larger collapse in CVG market value
  • Summary: Convergence’s reward distributor proxy (0x2b083beaaC310CC5E190B1d2507038CcB03E7606) let anyone call claimMultipleStaking() with an arbitrary claimContracts array. The protocol meant those addresses to be canonical Convergence staking-position contracts implementing claimCvgCvxMultiple(address), but the function never proved that assumption onchain. The attacker deployed malicious contract 0xee45384d4861b6fb422dfa03fbdcc6e29d7beb69, inserted it as the only claim source, and made it return an absurd CVG claim amount. The deeper failure was not just fake staking contract in, big number out; it was that the whole certifier-to-mint chain stayed caller-authored: the attacker chose the reward-certifying contract, that contract authored the claimed amount, and the distributor then treated that return value as canonical reward entitlement worthy of minting about 58M CVG. The attacker dumped the fresh supply through Curve pools for roughly 60 WETH and 15,900 FRAX, collapsing CVG by about 99%.
  • Trigger / entry point:
    • The vulnerable surface was CvxRewardDistributor.claimMultipleStaking(ICvxStakingPositionService[] claimContracts, address _account, uint256 _minCvgCvxAmountOut, bool _isConvert, uint256 cvxRewardCount).
    • That path was meant to aggregate claims across multiple legitimate Convergence staking contracts.
    • Instead of deriving or checking a canonical allowlist / registry of staking-position services, it accepted a caller-supplied claimContracts array and called each contract through the expected interface.
    • Multiple incident reconstructions state that a post-audit modification removed the line that had checked the supplied input, turning the surface from a convenience helper into a mint primitive.
  • Root cause:
    • Convergence authenticated the wrong object. It treated an arbitrary caller-supplied contract as if it were a canonical staking-reward source.
    • The validation failure was not merely bad math; it was a provenance failure. The distributor never proved that the contract reporting claimable rewards belonged to Convergence’s real staking graph.
    • The broken security object was the whole certifier-to-mint path: claimContracts[] selected the certifier, claimCvgCvxMultiple() authored the claimed amount, and downstream mint logic treated that return value as if it already described a real Convergence reward entitlement.
    • Once that fake root object was accepted, the attacker did not need a second bug. The counterfeit certifier could directly manufacture mint-worthy reward truth.
    • The incident is therefore closer to fake integration endpoint / root-of-trust substitution than to a generic accounting typo: the protocol let the attacker choose who was allowed to define reward reality.
  • Exploit path:
    1. The attacker deployed malicious helper contract 0xee45384d4861b6fb422dfa03fbdcc6e29d7beb69 implementing the expected claimCvgCvxMultiple(address) signature.
    2. They called CvxRewardDistributor.claimMultipleStaking() in transaction 0x636be30e58acce0629b2bf975b5c3133840cd7d41ffc3b903720c528f01c65d9 and supplied that malicious contract as the sole entry in claimContracts.
    3. When the distributor called the fake staking contract, the malicious implementation returned a gigantic synthetic CVG claim amount. Public PoCs reproduce this as type(uint256).max - CVG.totalSupply().
    4. Because the distributor never verified that the contract was one of Convergence’s real staking modules, it accepted the return value as authentic earned rewards.
    5. The contract then minted roughly 58M CVG to the attacker-controlled account.
    6. The attacker immediately dumped the minted tokens through Curve CVG/ETH pool 0x004C167d27ADa24305b76D80762997Fa6EB8d9B2 and Curve CVG/FRAX pool 0xa7B0E924c2dBB9B4F576CCE96ac80657E42c3e42.
    7. The cash-out realized about 60 WETH and 15,900 FRAX, while the much larger damage landed on CVG holders through near-total price collapse.
  • What the attacker needed to be true:
    • claimMultipleStaking() had to accept arbitrary staking-contract addresses from the caller.
    • The reward distributor had to trust the return value of claimCvgCvxMultiple() without proving the caller-supplied contract was canonical.
    • The mint path had to convert returned claimable amount directly into new CVG supply.
    • Enough secondary-market liquidity had to exist in CVG/ETH and CVG/FRAX pools for the attacker to crystallize value before the token repriced to near zero.
  • Technical anatomy worth remembering:
    • This was a reward-certifier authenticity failure. The decisive bug was not just missing input validation; it was that Convergence let a user choose the contract that would certify how much reward had been earned.
    • The malicious contract only needed interface compatibility, not protocol legitimacy. That makes this a clean fake-integration-endpoint case.
    • The full trust chain stayed attacker-authored: choose fake staking contract → satisfy the expected interface → return a fabricated (cvgClaimable, rewards[]) tuple → let the distributor convert that tuple into real minted CVG. That is the sharper preserved mechanism than unvalidated array parameter alone.
    • The fake contract did not need reentrancy, governance capture, or oracle games. It simply returned a fabricated claim amount and relied on Convergence to treat that fabricated amount as protocol truth.
    • The preserved invariant is: reward source, reward entitlement, and mint amount must come from canonical protocol state together. If the user can choose the reward-certifying contract, the mint path is already compromised.
    • The post-audit-change detail matters because it shows how easily a protocol can fall out of audit scope: a single removed validation line converted a reward helper into an unbounded emission printer.
  • Representative transactions and contracts:
    • Main exploit transaction: 0x636be30e58acce0629b2bf975b5c3133840cd7d41ffc3b903720c528f01c65d9
    • Exploiter EOA: 0x03560a9d7a2c391fb1a087c33650037ae30de3aa
    • Malicious fake staking contract: 0xee45384d4861b6fb422dfa03fbdcc6e29d7beb69
    • Vulnerable reward distributor proxy: 0x2b083beaaC310CC5E190B1d2507038CcB03E7606
    • Reported implementation contract: 0x47c69e8c909ce626Af73c955A5e34A20B7c71f19
    • CVG token: 0x97efFB790f2fbB701D88f89DB4521348A2B77be8
    • Cash-out pools used in public PoCs: Curve CVG/ETH 0x004C167d27ADa24305b76D80762997Fa6EB8d9B2 and Curve CVG/FRAX 0xa7B0E924c2dBB9B4F576CCE96ac80657E42c3e42
  • Response / mitigation:
    • Convergence publicly acknowledged the incident and warned users to withdraw staked assets as a precaution while the team investigated.
    • Public incident analyses report — and later postmortem quotations preserve — that the team attributed the exploit to a post-audit gas-optimization change that removed the line validating the input given to the function.
    • The practical lesson was not only audit more; it was treat post-audit trust-boundary changes as new unaudited code, especially when they decide which external contracts may certify rewards or balances.

Pattern tags

  • account-confusion-root-of-trust-substitution
  • issuance-redemption-solvency-asymmetry
  • The exploit only needed one counterfeit trust anchor: once claimMultipleStaking() let the attacker choose the contract that defined reward truth, the honest downstream mint transferable CVG -> dump into live pools leg converted that local authentication error into real value extraction.
  • This is a clean fake-integration-endpoint incident rather than a generic reward math bug. The decisive question was not how CVG emissions were computed after a canonical source spoke, but who got to speak as the canonical source in the first place.

Why the exploit worked

  • Convergence collapsed three different questions into one helper-shaped assumption: what exact contract is allowed to certify staking rewards?, what exact claim does that contract certify for this account?, and does the certified amount deserve immediate mint authority against CVG supply? The attacker only had to win the first question with their own contract for the other two to be granted implicitly.
  • Implements claimCvgCvxMultiple(address) was treated as if it were a provenance proof. That authenticated an attacker-deployed endpoint as a legitimate reward source even though the protocol had not pinned the address to a registry, immutable config slot, or other protocol-owned staking graph.
  • Once the counterfeit certifier was accepted, the rest of the path stayed locally honest. The fake contract returned a giant claim amount, and the distributor faithfully converted that returned value into fresh CVG plus associated reward transfers. No oracle manipulation, reentrancy, or secondary authorization bug was needed after the root-authenticity failure.
  • The exit side stayed economically real. Freshly minted CVG still had immediate swap access against live Curve liquidity, so one counterfeit reward-certifier call became both an emission blowout and an instantly realizable treasury / market-structure loss.
  • This makes Convergence a strong corpus exemplar: the protocol did not merely forget an input check. It let a caller-authored external contract graduate from helper-shaped address in calldata to canonical reward truth and then to mint-worthy supply authority.

Three reward-certifier promotions this incident collapsed

  • Some interface-compatible staking / claim contract exists is not the same thing as this exact contract is the canonical Convergence reward-certifier for this user's staking position.
  • This contract returned one claim tuple is not the same thing as that tuple is the canonical earned-reward entitlement the protocol should publish as CVG truth.
  • That accepted entitlement exists is not the same thing as it still deserves immediate mint authority plus live secondary-market cash-out against CVG liquidity right now.
  • Convergence collapsed those promotions into one helper-shaped assumption: if the caller supplied a contract with the right signature, the protocol let that contract define entitlement reality and then immediately monetized that reality through fresh supply creation.

Detection and audit pivots

  • Enumerate every reward, staking, or emission helper that accepts caller-supplied contracts, adapters, or position services. Ask which of those surfaces are protocol-pinned and which are only interface-compatible.
  • Diff single-claim and batch-claim code paths for registry or allowlist checks, especially after gas-optimization refactors. Convergence is a reminder that deleting one validation line can silently move a path from aggregator to mint printer.
  • Fuzz reward helpers with hostile interface-compatible contracts that return type(uint256).max, absurd but in-range emission values, duplicated reward arrays, or empty reward arrays plus giant mint claims. The question is not just whether the call reverts, but whether any returned tuple can become live emission truth without provenance proof.
  • Trace where reward entitlement is actually bound to canonical protocol state. If the system can reach mint(amount) before proving this exact contract was allowed to certify this exact user's claim, the entitlement path is still caller-authored.
  • Monitor for unexpected certifier address, impossible one-call emissions, single-call total-supply jumps, and fresh mint immediately swapped into secondary-market pools. Convergence-style incidents often advertise themselves in those control-plane signals before governance or users can reason through the full exploit.

Failure-prevention checklist

  • Derive eligible staking / reward-source contracts from canonical protocol state inside claimMultipleStaking() rather than letting the caller supply the contracts that define reward truth.
  • If the protocol truly needs caller-supplied selectors, validate each one against a registry or immutable allowlist before any external call or reward accumulation begins.
  • Keep reward source identity, reward entitlement identity, and mint amount inside one fused security object. The same path that decides which contract may certify rewards should also constrain what account, tranche, and amount that contract is allowed to certify.
  • Make the batch helper resolve reward rights through one shared internal consume primitive instead of treating each external contract return value as self-authenticating payout truth.
  • Treat interface compatibility as a necessary but weak property. implements claimCvgCvxMultiple(address) should never be enough to earn mint authority.
  • Gate post-audit changes that touch trust boundaries, registries, helper inputs, or downstream mint logic behind fresh review, explicit diff signoff, and regression tests.
  • Add regression tests for counterfeit certifier contracts, duplicate batch members, absurd return amounts, and new contract returns max uint style payloads.
  • Bound loss even if one fake certifier slips through once: per-call mint caps, emission-rate limits, route-local pauses, delayed conversion, or staged settlement should stop one counterfeit claim source from printing protocol-scale supply instantly.
  • Monitor reward minting against expected emission envelopes and page operators on impossible jumps in one call, one account, or one helper route.

Audit prompts

  • What exact canonical object proves that one contract is allowed to certify CVG rewards for one user, and where is that identity loaded or recomputed before claimMultipleStaking() trusts it?
  • Can a caller supply a contract that is interface-compatible but not protocol-canonical, and if not, which concrete registry or immutable config blocks it?
  • Where does the code bind reward-certifying contract, claimable amount, and mint recipient into one machine-checkable entitlement instead of trusting a raw external return tuple?
  • If a malicious contract returned type(uint256).max, what invariant would stop that value from becoming live minted supply?
  • Do single-claim and batch-claim cousins share one internal entitlement-consumption primitive, or are reviewers relying on several manually synchronized code paths?
  • Which post-audit edits changed who or what is allowed to define reward reality, and were those edits reviewed as security-boundary changes rather than convenience refactors?
  • What onchain control caps or delays issuance if one reward-certifier path lies once?
  • Which production monitor would have fired first: unexpected helper address, impossible per-call emission, anomalous total-supply jump, or oversized conversion into secondary-market pools?

Lessons that should already be standard

  • Reward distributors are often mint authorities in disguise. If they trust external contracts to report entitlement, those contracts belong inside the mint threat model.
  • The protocol must choose the reward certifier, not the user. Once the caller can nominate the contract that defines earned rewards, every later check can be locally correct and still be attacker-authored fiction.
  • Implements the right interface is not a provenance proof. Counterfeit certifiers monetize exactly because interface shape is cheaper to fake than protocol legitimacy.
  • Post-audit trust-boundary edits are new unaudited attack surface. A single removed validation line can convert a helper into an emission printer.
  • Emission systems need bounded-loss design too. Even when the printed asset is the protocol’s own token, the balance-sheet damage can spread through market structure, governance weight, and user solvency far beyond the attacker’s immediate cash-out.
  • Impossible reward amounts should fail structurally, not only socially. If a single helper call can ask the protocol to mint essentially the remaining uint256 supply space, the entitlement boundary is already underdesigned.

Why this incident matters

  • It is a concise EVM example of account confusion outside the more familiar fake-collateral or fake-market cases. Here, the attacker supplied a counterfeit reward source rather than counterfeit collateral.
  • It shows that yield and staking distributors can carry the same root-of-trust problem as bridges, vault migrators, and lending helpers: once a caller chooses the object that defines reality, every later check can be perfectly consistent and still be attacker-authored fiction.
  • It also preserves an important market-structure lesson: even when realized cash-out is only a few hundred thousand dollars, the protocol can still suffer a much larger governance-token integrity failure once unbacked emissions hit live secondary-market liquidity.

Internal linkages

Control surface