6. Selfie
https://www.damnvulnerabledefi.xyz/challenges/selfie/
Last updated
https://www.damnvulnerabledefi.xyz/challenges/selfie/
Last updated
A lending pool offering flash loans of DVT tokens.
Includes a fancy governance mechanism to control it.
You start with no DVT tokens in balance, and the pool has 1.5 million.
Your goal is to take them all.
SelfiePool has a drainAllFunds
function that can only be called by the SimpleGovernance contract.
Let us examine if we can subvert the governance contract for our purposes.
Governance is based on the DVT token:
Voting power is based on the proportion of number of tokens held against half of total supply
This means that if msg.caller has sufficient DVT tokens such that its balance > halfTotalSupply, msg.caller has sufficient voting power and therefore can queue a governance action successfully.
Attacker to take the largest flash loan from SelfiePool possible,
Using voting power from flash loan to pass a governance action via SimpleGovernance using queueAction and executeAction
call drainAllFunds with beneficiary as attacker address
queueAction takes in an address
, calldata
, and weiAmount
as parameters. These fields are stored in a struct
address: target contract address to take action on ("receiver")
calldata: abi encoded function signature:
weiAmount: this is the value field for target.call{value:
value
}(data)
basically how much ether to send over
executeAction
external, payable
Anyone can call executeAction, passing an actionId
only constraints are:
action has not been executed yet
actionToExecute.executedAt == 0
action is ready to be executed (delay has been exceeded)
The core of executeAction is
functionCallWithValue is from OpenZepplin's Address library:
Fundamentally, it is a low-level call, where value is the weiAmount and data is the data we passed earlier in queueAction as parameters.
Initiate flashloan with borrow()
selfiePool contract will callback receivetokens
; this will lead to execution of the attack.
queueAction: drainAllfunds
return borrowed tokens
executeAction can be called by anyone after the defined delay has passed. Therefore we do not need to execute this via contract.
this is needed because queueAction calls _hasEnoughVotes
which calculates addresses balances based on snapshots.
Note
The attack component must be shoeboxed into the callback function instead of the initial borrow function.
Execution of flashloan() only completes after the tokens are returned. This means that the following will not work:
Since the flashloan will be settled before the attack code is executed -> without the benefit of borrowed tokens.