GoodDollar Fake-Interest Mint
Exploit Transactions
0x726459a46839c915ee2fb3d8de7f986e3c7391c605b7a622112161a84c7384d0Victim Addresses
0xa150a825d425b36329d8294eef8bd0fe68f8f6e0Ethereum0x0c6c80d2061afa35e160f3799411d83bdeea0a5aEthereum0x67c5870b4a41d4ebef24d2456547a03f1f3e094bEthereumLoss Breakdown
Similar Incidents
CivTrade Fake-Pool Callback Drain
33%Unlimited-Mint Collateral Used to Over-Mint Debt Token
32%Conic ETH Oracle Reentrancy
31%CVG staking supply drain from reward-mint inflation bug
31%BUILD Governance Takeover and Unlimited Mint ACT Exploit
30%BRO-SOLV Callback Double-Mint Value Amplification
30%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
0x0c6c80d2061afa35e160f3799411d83bdeea0a5adelegated to implementation0x4a37a8d7cdb43d89b4dbd7ecfaeaf9bd39e24929. - GoodReserveCDai proxy
0xa150a825d425b36329d8294eef8bd0fe68f8f6e0delegated to implementation0x2793a5887f53b025f49f7a9249d66f4671bce29b. - GoodMarketMaker proxy
0xdac6a0c973ba7cf3526de456affa43ab421f659fdelegated to implementation0xb3ff03c8a42506ba58b1127cd4b5928c7ed70a03.
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:
- Earlier public setup transactions deployed helper
0x9a5c...and orchestrator0xf06a...and funded the exploit sender. - The exploit transaction used a Balancer flash loan, Compound cETH/cDAI, GoodDollar reserve calls, and
GoodFundManager.collectInterest. - 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: funded0x6738..., deployed helper0x9a5c..., and received final DAI, G$, and residual WETH.0x6738fa889ff31f82d9fe8862ec025dbe318f3fde: deployed orchestrator0xf06a...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.
- Adversary setup. Public transactions
0x92e47eee...,0xb92e1642...,0xaf9a9ef5..., and0xf6fb004b...deployed the helper and orchestrator, funded the exploit sender, and approved the orchestrator to pull helper-owned WETH. - 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
228488816255120718cDAI to buy6112337690790raw G$ units from the reserve. - Fake interest minting and monetization. The orchestrator transferred cDAI to helper
0x9a5c..., calledcollectInterestwith that helper, let the helper buy G$ twice during the callback window, causedmintUBIto 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...lost2765737667303848raw cDAI units during the exploit transaction. - Attacker EOA
0x6c08...gained1021339424425raw G$ units. - Distribution recipients
0xa3247276dbcc76dd7705273f766eb3e8a5ecf4a5and0xd5d11ee582c8931f336fbcd135e98cee4db8ccb0gained346380923987and38486769331raw 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 cDAI0x5d3a536e4d6dbd6114cc1ead35777bab948e3643, Compound cETH0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5