Skip to content
On this page

The Selfie Exploit: How a Flash Loan Can Hijack Your DeFi Governance

Imagine a brand-new DeFi lending pool, gleaming with the promise of innovative financial services. It offers instant, zero-collateral flash loans of its native DVT tokens, and boasts a "fancy governance mechanism" to ensure community control. What could possibly go wrong?

This is the premise of the "Selfie" challenge from Damn Vulnerable DeFi v4. You start with nothing, but 1.5 million DVT tokens are locked in the pool, waiting to be rescued and deposited into a designated recovery account. This isn't just about stealing funds; it's about cleverly manipulating the very governance system designed to protect them.

The Setup: A Triad of Contracts

At the heart of this challenge lies a triad of interconnected smart contracts:

  1. SelfiePool.sol: This is the lending pool itself. It allows users to take flash loans of DVT tokens. Critically, it also has an emergencyExit(address receiver) function. However, this function is protected by an onlyGovernance modifier, meaning only the SimpleGovernance contract can call it.
  2. SimpleGovernance.sol: This contract holds the keys to the emergencyExit. It allows actions to be queueActiond and later executeActiond. To queueAction, the caller needs to have "enough votes" – specifically, more than half of the total supply of the governance token. Once an action is queued, there's a mandatory ACTION_DELAY_IN_SECONDS (2 days) before it can be executed.
  3. DamnValuableVotes.sol: This is the DVT token. Crucially, it's not just a standard ERC20; it's also the voting token for the SimpleGovernance contract. It includes a delegate function, allowing token holders to assign their voting power to another address.

The challenge's apparent security lies in the governance mechanism: you can't just call emergencyExit directly. You need the governance to approve it, and that requires a majority vote. How can an attacker with no DVT tokens possibly achieve that?

The Vulnerability Revealed: A Fleeting Majority

Here's where the seemingly secure system takes a dramatic turn. The SimpleGovernance contract determines voting power based on the getVotes(who) function of the DamnValuableVotes token. To queueAction, you need _votingToken.getVotes(msg.sender) > _votingToken.totalSupply() / 2.

The key insight is the combination of flash loans and the dual nature of the DVT token as both liquidity and governance power.

What if you could temporarily acquire a dominant share of these voting tokens? A flash loan provides exactly that ephemeral opportunity. You can borrow all the DVT tokens from the SelfiePool, hold them for a single transaction, and then return them. This brief moment of immense liquidity gives the attacker unprecedented voting power.

The Attack in Action: A Daring Two-Step Heist

Our attacker, represented by the MyContract exploit contract, executes the heist in two phases:

Phase 1: The Instant Coup (Inside the Flash Loan Callback)

  1. Flash Loan Initiation: MyContract initiates a flash loan from SelfiePool, requesting all of the DVT tokens held within the pool (1.5 million).
  2. The Ephemeral Window: When the SelfiePool grants the flash loan, it calls MyContract's onFlashLoan function. This is the attacker's golden moment.
  3. Delegate and Dominate: While holding all 1.5 million DVT tokens, MyContract immediately calls token.delegate(address(this)). This instantly grants MyContract majority voting power within the SimpleGovernance system.
  4. Queue the Heist: Capitalizing on this fleeting dominance, MyContract swiftly calls governance.queueAction(address(pool), 0, data), where data encodes a call to emergencyExit(recovery). Since MyContract now satisfies the _hasEnoughVotes condition (it controls >50% of the total DVT supply), the action is successfully queued!
  5. Repay and Retreat: After queueing the action, MyContract approves the SelfiePool to take back its tokens to satisfy the flash loan repayment. The DVT tokens return to the pool, and the system appears to revert to its normal state. But the seed of destruction has been planted.

Phase 2: The Delayed Execution (After the Waiting Game)

  1. The Waiting Game: The SimpleGovernance contract requires a 2-day delay (ACTION_DELAY_IN_SECONDS) before a queued action can be executed. The attacker must wait patiently.
  2. Final Strike: After the 2 days have passed, MyContract calls governance.executeAction(actionId), targeting the emergencyExit action it had previously queued.
  3. Mission Accomplished: The executeAction call triggers the emergencyExit function in SelfiePool, which then transfers all 1.5 million DVT tokens from the pool directly to the designated recovery account.

The Takeaway: Governance Requires Robust Design

The "Selfie" challenge serves as a stark reminder of the profound impact flash loans can have on DeFi protocols, especially those with governance mechanisms tied to liquid pool tokens. The apparent security of a multi-day voting delay was completely circumvented by the ability to temporarily amass overwhelming voting power, even if only for the duration of a single transaction.

The lesson is clear: Governance mechanisms, especially those tied directly to the liquidity of a protocol's core assets, must be rigorously designed to withstand such "temporary majority" attacks. Relying solely on a vote count derived from liquid, flash-loanable tokens, without additional safeguards (like time-weighted average voting, minimum lock-up periods, or differentiated governance tokens), leaves a critical vulnerability open.

The "Selfie" exploit isn't just a clever hack; it's a critical case study in DeFi security, showcasing how unexpected interactions between seemingly innocuous features can lead to devastating consequences. It's a "selfie" of a system caught off guard by its own reflection.


Built with AiAda