All incidents

GoodDollar Fake-Interest Mint

Share
Dec 16, 2023 23:53 UTCAttackLoss: 625,140.68 DAIPending manual check1 exploit txWindow: Atomic
Estimated Impact
625,140.68 DAI
Label
Attack
Exploit Tx
1
Addresses
3
Attack Window
Atomic
Dec 16, 2023 23:53 UTC → Dec 16, 2023 23:53 UTC

Exploit Transactions

TX 1Ethereum
0x726459a46839c915ee2fb3d8de7f986e3c7391c605b7a622112161a84c7384d0
Dec 16, 2023 23:53 UTCExplorer

Victim Addresses

0xa150a825d425b36329d8294eef8bd0fe68f8f6e0Ethereum
0x0c6c80d2061afa35e160f3799411d83bdeea0a5aEthereum
0x67c5870b4a41d4ebef24d2456547a03f1f3e094bEthereum

Loss Breakdown

625,140.68DAI

Similar Incidents

Root Cause Analysis

GoodDollar Fake-Interest Mint

1. Incident Overview TL;DR

In Ethereum mainnet block 18802015, transaction 0x726459a46839c915ee2fb3d8de7f986e3c7391c605b7a622112161a84c7384d0 let an unprivileged attacker cluster turn self-supplied cDAI into protocol-accounted reserve interest inside historical GoodDollar contracts. The attacker used earlier public setup transactions to deploy helper contract 0x9a5cd1145791b29ac4e68df3bf8e30d2167daa76 and orchestrator contract 0xf06ab383528f51da67e2b2407327731770156ed6, then used the orchestrator to take a public Balancer WETH flash loan, mint Compound collateral, borrow DAI, mint cDAI, buy a large G$ position, abuse GoodDollar’s interest-collection path, sell the inflated G$ back into the reserve, redeem to DAI, repay all debt, and transfer profit to EOA 0x6c08f56ff2b15db7ddf2f123f5bffb68e308161b.

The root cause is an access-control and accounting failure across historical GoodFundManager and GoodReserveCDai implementations. Historical GoodFundManager.collectInterest(address[],bool) accepted arbitrary attacker-supplied staking contracts and called their collectUBIInterest callbacks without validation. Historical GoodReserveCDai.mintUBI(uint256,uint256,ERC20) then treated the reserve’s post-callback cDAI balance minus a stale pre-callback snapshot as interest. Historical GoodMarketMaker monetized that fabricated cDAI delta through mintInterest and mintExpansion, producing fresh G$ that the attacker then sold for more cDAI and ultimately 625140.676705135213042766 DAI of profit before gas and the observed 50249695 wei WETH shortfall.

2. Key Background

GoodDollar’s historical reserve pipeline split responsibility across three contracts that all executed in the exploit transaction:

  • GoodFundManager proxy 0x0c6c80d2061afa35e160f3799411d83bdeea0a5a delegated to implementation 0x4a37a8d7cdb43d89b4dbd7ecfaeaf9bd39e24929.
  • GoodReserveCDai proxy 0xa150a825d425b36329d8294eef8bd0fe68f8f6e0 delegated to implementation 0x2793a5887f53b025f49f7a9249d66f4671bce29b.
  • GoodMarketMaker proxy 0xdac6a0c973ba7cf3526de456affa43ab421f659f delegated to implementation 0xb3ff03c8a42506ba58b1127cd4b5928c7ed70a03.

GoodFundManager.collectInterest was the protocol entrypoint that collected staking yield, measured reserve balances, and called reserve.mintUBI. The historical implementation captured the reserve’s DAI and cDAI balances before it iterated over _stakingContracts, then invoked each contract’s collectUBIInterest(reserveAddress) callback. There was no whitelist or isActiveContract gate on that callback path in the implementation that actually ran during the incident.

GoodReserveCDai.buy and sell moved cDAI and G$ against the GoodDollar reserve curve. GoodReserveCDai.mintUBI minted fresh G$ after staking collection. Historical mintUBI accepted the caller-provided _startingCDAIBalance and computed interestInCdai as the reserve’s live cDAI balance minus that stale snapshot. GoodMarketMaker.mintInterest then increased both gdSupply and reserveSupply using the supplied delta, while mintExpansion minted more G$ from the reserve-ratio expansion path.

This arrangement meant that any contract able to call collectInterest with an attacker-controlled staking helper could inject cDAI into the reserve during the callback window and have that attacker principal reclassified as reserve interest. The exploit did not require private keys, privileged roles, or hidden off-chain data. It only required public on-chain primitives: Balancer flash liquidity, Compound, GoodDollar, and the permissionless historical collectInterest entrypoint.

3. Vulnerability Analysis & Root Cause Summary

This incident is an ATTACK-category ACT exploit, not MEV arbitrage and not a privileged compromise. The violated invariant is straightforward: mintUBI must mint G$ only against genuine reserve interest or fresh DAI converted by the reserve, not against attacker principal routed through a staking callback. Historical GoodFundManager broke the trust boundary by treating arbitrary callback contracts as legitimate staking collectors. Historical GoodReserveCDai then broke accounting by trusting startingCDAIBalance captured before those callbacks rather than authoritative post-buy reserve accounting. Historical GoodMarketMaker completed the exploit path by minting G$ against the fabricated interestInCdai value and then applying an additional expansion mint on top of that fabricated growth. The seed trace shows both fake-interest mints, the two helper reserve buys that caused them, and the final reserve sell and DAI redemption that realized profit.

The core victim-side code that enabled the exploit was:

function collectInterest(
    address[] calldata _stakingContracts,
    bool _forceAndWaiverRewards
) external {
    ...
    uint256 currentBalance = daiToken.balanceOf(reserveAddress);
    uint256 startingCDAIBalance = iToken.balanceOf(reserveAddress);
    for (uint256 i = _stakingContracts.length; i > 0; i--) {
        if (_stakingContracts[i - 1] != address(0x0)) {
            IGoodStaking(_stakingContracts[i - 1]).collectUBIInterest(
                reserveAddress
            );
        }
    }
    uint256 daiToConvert = daiToken.balanceOf(reserveAddress) - currentBalance;
    (gdUBI, interestInCdai) = GoodReserveCDai(reserveAddress).mintUBI(
        daiToConvert,
        startingCDAIBalance,
        iToken
    );
}
function mintUBI(
    uint256 _daiToConvert,
    uint256 _startingCDAIBalance,
    ERC20 _interestToken
) external returns (uint256, uint256) {
    cERC20(cDaiAddress).mint(_daiToConvert);
    uint256 interestInCdai = _interestToken.balanceOf(address(this)) -
        _startingCDAIBalance;
    uint256 gdInterestToMint = getMarketMaker().mintInterest(
        _interestToken,
        interestInCdai
    );
    uint256 gdExpansionToMint = getMarketMaker().mintExpansion(_interestToken);
    ...
}

4. Detailed Root Cause Analysis

4.1 Code-Level Breakpoint

The decisive breakpoint is the handoff between historical GoodFundManager and historical GoodReserveCDai. GoodFundManager captured startingCDAIBalance before it called attacker-controlled callbacks. Those callbacks were free to transfer cDAI into the reserve by calling reserve.buy. GoodReserveCDai then treated the difference between the live reserve cDAI balance and the old snapshot as protocol interest. There was no provenance check that separated attacker principal from actual staking yield.

Historical GoodMarketMaker converted that fabricated delta into fresh G$:

function mintInterest(ERC20 _token, uint256 _addTokenSupply)
    public
    returns (uint256)
{
    _onlyReserveOrAvatar();
    _onlyActiveToken(_token);
    if (_addTokenSupply == 0) {
        return 0;
    }
    uint256 toMint = calculateMintInterest(_token, _addTokenSupply);
    ReserveToken storage reserveToken = reserveTokens[address(_token)];
    reserveToken.gdSupply += toMint;
    reserveToken.reserveSupply += _addTokenSupply;
    return toMint;
}

Once interestInCdai was fabricated, the downstream mint path behaved exactly as coded: the market maker updated gdSupply and reserveSupply, the reserve minted UBI to the distribution helper, and the attacker’s existing G$ inventory became more valuable against the bonding curve.

4.2 Seed-Trace Evidence

The seed execution trace for tx 0x726459a46839c915ee2fb3d8de7f986e3c7391c605b7a622112161a84c7384d0 shows the exploit sequence directly:

0x0c6C80D2061afA35E160F3799411d83BDEEA0a5A::collectInterest([0x9A5c...], true)
0x4A37A8D7cdb43D89b4DBD7ecFAEaF9bD39E24929::collectInterest([0x9A5c...], true) [delegatecall]
0x9A5cD1145791B29Ac4E68dF3Bf8E30d2167daA76::collectUBIInterest(0xa150...)
0xa150a825d425B36329D8294eeF8bD0fE68f8F6E0::buy(12025727171322144, 1, 0x9A5c...)
0xa150a825d425B36329D8294eeF8bD0fE68f8F6E0::mintUBI(0, 231266102146506400, cDAI)
GoodMarketMaker::mintInterest(cDAI, 12025727171322144)
GoodMarketMaker::mintExpansion(cDAI)

The same pattern repeated a second time inside the same transaction:

0x0c6C80D2061afA35E160F3799411d83BDEEA0a5A::collectInterest([0x9A5c...], true)
0x9A5cD1145791B29Ac4E68dF3Bf8E30d2167daA76::collectUBIInterest(0xa150...)
0xa150a825d425B36329D8294eeF8bD0fE68f8F6E0::buy(12299218063084614, 1, 0x9A5c...)
0xa150a825d425B36329D8294eeF8bD0fE68f8F6E0::mintUBI(0, 230992611254743930, cDAI)
GoodMarketMaker::mintInterest(cDAI, 12299218063084614)
GoodMarketMaker::mintExpansion(cDAI)

These calls satisfy the non-monetary success predicate recorded in root_cause.json: attacker-supplied cDAI transferred into the reserve during collectInterest was classified as reserve interest and monetized into fresh G$ issuance. The trace also shows the exploit closeout:

0xa150a825d425B36329D8294eeF8bD0fE68f8F6E0::sell(5090998266365, 1, 0xF06A..., 0xF06A...)
CErc20Delegate::transfer(0xF06A..., 230707119441701793)
CErc20Delegate::redeemUnderlying(54363588496665202261586443)
CErc20Delegate::repayBorrow(54363588496665202261586443)
CErc20Delegate::redeem(2765737667303848)

The reserve sell yielded 230707119441701793 cDAI to the attacker contract. After cDAI redemption and borrow repayment, the trace records 625140676705135213042766 DAI transferred from orchestrator 0xf06a... to attacker EOA 0x6c08..., plus a residual G$ balance sent to the same EOA. The observed WETH shortfall was 50249695 wei, and the seed sender paid 0.10805968495570144 ETH in gas.

4.3 Why This Is ACT

The exploit sequence used only public, permissionless infrastructure:

  1. Earlier public setup transactions deployed helper 0x9a5c... and orchestrator 0xf06a... and funded the exploit sender.
  2. The exploit transaction used a Balancer flash loan, Compound cETH/cDAI, GoodDollar reserve calls, and GoodFundManager.collectInterest.
  3. No privileged signer, admin role, leaked secret, or private orderflow was necessary.

That matches the ACT definition recorded in root_cause.json: any unprivileged adversary able to deploy contracts and access public liquidity could realize the same opportunity against the historical implementations.

5. Adversary Flow Analysis

The adversary cluster in the root-cause artifact is supported by the on-chain evidence:

  • 0x6c08f56ff2b15db7ddf2f123f5bffb68e308161b: funded 0x6738..., deployed helper 0x9a5c..., and received final DAI, G$, and residual WETH.
  • 0x6738fa889ff31f82d9fe8862ec025dbe318f3fde: deployed orchestrator 0xf06a... and sent the exploit transaction.
  • 0x9a5cd1145791b29ac4e68df3bf8e30d2167daa76: attacker-controlled staking helper used as the fake interest collector.
  • 0xf06ab383528f51da67e2b2407327731770156ed6: attacker orchestrator that executed the flash-loan and unwind sequence.

The flow unfolded in three stages.

  1. Adversary setup. Public transactions 0x92e47eee..., 0xb92e1642..., 0xaf9a9ef5..., and 0xf6fb004b... deployed the helper and orchestrator, funded the exploit sender, and approved the orchestrator to pull helper-owned WETH.
  2. Flash loan and curve priming. In the seed transaction, the orchestrator took a Balancer WETH flash loan, minted cETH collateral, borrowed all available DAI from cDAI, minted cDAI with that DAI, and spent 228488816255120718 cDAI to buy 6112337690790 raw G$ units from the reserve.
  3. Fake interest minting and monetization. The orchestrator transferred cDAI to helper 0x9a5c..., called collectInterest with that helper, let the helper buy G$ twice during the callback window, caused mintUBI to treat those buys as interest, then sold the attacker’s main G$ position and redeemed back into DAI.

The two fake-interest loops minted G$ to GoodDollar’s distribution helper and to bridged recipients, while simultaneously improving the attacker’s exit price on the reserve curve. The root-cause artifact records the resulting fake UBI mint sizes as 201934705621 and 182932987697 raw G$ units. After the manipulation, the orchestrator sold 5090998266365 raw G$ units, redeemed to DAI, repaid Compound and Balancer, and forwarded the net proceeds to 0x6c08....

6. Impact & Losses

The direct measurable loss recorded in the root-cause artifact is 625140676705135213042766 raw DAI units, or 625140.676705135213042766 DAI. That amount matches both the seed trace redemption flow and the session’s loss metadata requirements.

Additional state impact is visible in the collected balance diff:

  • Reserve proxy 0xa150... lost 2765737667303848 raw cDAI units during the exploit transaction.
  • Attacker EOA 0x6c08... gained 1021339424425 raw G$ units.
  • Distribution recipients 0xa3247276dbcc76dd7705273f766eb3e8a5ecf4a5 and 0xd5d11ee582c8931f336fbcd135e98cee4db8ccb0 gained 346380923987 and 38486769331 raw G$ units from the fake UBI mints.

The monetary success predicate in root_cause.json is therefore satisfied: the attacker ended with a large positive DAI balance after all debt and flash-loan repayments. The non-monetary predicate is also satisfied: unauthorized reserve interest accounting occurred because attacker-funded cDAI was counted as reserve interest during collectInterest.

7. References

  • Seed exploit transaction: 0x726459a46839c915ee2fb3d8de7f986e3c7391c605b7a622112161a84c7384d0
  • Helper deployment: 0x92e47eee5b1700df592cf1af3d7fec40c7411b83206e813c6bc811c1e4e9bf4c
  • Exploit orchestrator deployment: 0xaf9a9ef59414a93080147aefae9083f6a0b79074ed38988f76789b8c0636622c
  • Helper approval transaction: 0xf6fb004b9dfd04af8658be2f63e3f5b0a7118d6d881e0a624715d6d669e4bec7
  • Historical GoodFundManager implementation: 0x4a37a8d7cdb43d89b4dbd7ecfaeaf9bd39e24929
  • Historical GoodReserveCDai implementation: 0x2793a5887f53b025f49f7a9249d66f4671bce29b
  • Historical GoodMarketMaker implementation: 0xb3ff03c8a42506ba58b1127cd4b5928c7ed70a03
  • Victim reserve proxy: 0xa150a825d425b36329d8294eef8bd0fe68f8f6e0
  • Victim fund-manager proxy: 0x0c6c80d2061afa35e160f3799411d83bdeea0a5a
  • GoodDollar token: 0x67c5870b4a41d4ebef24d2456547a03f1f3e094b
  • Public liquidity and debt venues used in the exploit: Balancer Vault 0xba12222222228d8ba445958a75a0704d566bf2c8, Compound cDAI 0x5d3a536e4d6dbd6114cc1ead35777bab948e3643, Compound cETH 0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5