2
min read

DAO, Governance and Attacks

Published on
June 27, 2024

DAO is Decentralised Autonomous Organisation. Essentially, it’s a set of smart contracts the determine how its participants/members make decisions. Let’s look at a small graph to easier understand it.

We’ve got 2 contracts: one is where we store the funds, the second is where we make proposals and vote. It can also be the same contract, depends on protocol’s implementation. The users or DAO members can create Proposals on Governance and other members can vote for or against it. If a certain threshold is passed, then the proposal goes through.

But what these proposals can do, you may ask. For example, the proposal can be to take DAO’s funds and donate it to the other protocol OR invest in liquidity pool/vault and earn yield for DAO members. If the proposal “yes” votes didn’t reach the required threshold, then these actions are not taken and proposal cancelled.

Another question you may have is how to get into DAO? One example can be purchasing DAO’s NFT (e.g. NounsDAO). You buy an NFT and its cost is sent into Treasury, or DAO’s funds. Then the members, or DAO’s NFT holders, can make proposals and vote on them.

Here come the attack vectors, how to manipulate proposal execution?

Common attack vectors

Make a malicious proposal

It can be as simple as making the proposal to move the entire treasury to the malicious actor. Of course, in a normal proposal the members won’t agree with that and will cancel the proposal. Hence, it can be something more granular, for example, we create a contract that will be responsible for moving DAO’s funds. Then, we, as a malicious actor, make a proposal to implement this contract to invest in another protocol. The members vote for it and our contract is applied. The malicious actor, who proposed this contract may know the flaw to use it and still all the funds, either send to another address controlled by them or simply withdraw from it.

Transferring voting power

Commonly, the voting power comes with the Governance Token. For example, owning an NFT gives you voting power or having ERC20 Governance Token and the more of them you have, the more voting power you get.

In that case, the malicious actor can vote, send their Governance Token to another address, then vote again and repeat. Basically, it becomes an infinite voting power.

Mitigation can be locking the Governance Token inside the contract until the proposal is not accepted or rejected. Hence, we cannot transfer the token to another address. For NFTs, we can create an array of <code-word>tokenId<code-word> which has already voted. Therefore, even if we transfer the NFT, we cannot vote with more than once.

Flash Loan the Governance Token

It’s very simple, if you can get a flash loan on the governance token, then you can significantly increase you voting power. The mitigation is the same as in the previous example: lock the funds in the contract after the user votes, so they cannot pay back the flash loan in the same transaction.

But here comes another attack vector. What if the attacker can flash loan the governance token, make the proposal reach a certain voting threshold to be accepted, then the proposal is passed in the same transaction and users retrieve their tokens? In that case the attacker can repay the flash loan. To mitigate this vulnerability, we have to make sure that you cannot vote and accept the proposal in the same transaction.

For example, we’ve got <code-word>vote<code-word> and <code-word>executeProposal<code-word> functions. The attack can flash loan the governance token, call <code-word>vote<code-word>, now we’re passing the voting threshold and call <code-word>executeProposal<code-word> in the same transaction to make the proposal go through and retrieve the governance token. If we can ensure the <code-word>vote<code-word> and <code-word>executeProposal<code-word> functions cannot be called in the same transaction, this attack vector is impossible.

Selfdestruct opcode in contracts associated with the proposal

Simply put, if proposal has a contract associated with it (for example, to ease the process of investing in other protocols) and this contract has a <code-word>selfdestruct<code-word> opcode somewhere in it, (and malicious actors have access to it) then the attacker can destroy the contract and change its logic to harm the DAO and its members. For example, similar attack vector was used on Tornado Cash.

Actual Finding Deep Dive

This vulnerability wasn’t explained above and it’s not an attack vector, but a flaw that you can check during audits. This is the vulnerability from FrankenDAO contest on Sherlock.

To become a member of FrankenDAO you have to buy and stake an NFT. When you stake it, the contract calculates token’s voting power through the function below and increases user’s voting power accordingly:

Variables we want to look at are <code-word>monsterMultiplier<code-word> and <code-word>baseVotes<code-word>. They’re responsible for how much voting power you receive after staking. For example, when you stake the NFT, <code-word>monsterMultiplier = 50<code-word> and <code-word>baseVotes = 20<code-word> (just an arbitrary but realistic values in protocol’s context). In that case, your voting power would be 10.

With time the admin decided to change <code-word>monsterMultiplier<code-word> to 60. If we look at <code-word>getTokenVotingPower<code-word> above again, with updated multiplier, your voting power would be 12 instead of 10. Now, we have to consider two factors:

1. When we stake, the contract calculates our voting power and assings it to the array:

Here, <code-word>newVotingPower<code-word> is the return value of <code-word>getTokenVotingPower<code-word>. We increment <code-word>votesFromOwnedTokens<code-word> and <code-word>tokenVotingPower<code-word> for the caller the caller. Hence, in the example we shared above, right now both these mappings will save 10.

2. Now, let’s look what happens if we decide to unstake after the <code-word>monsterMultiplier<code-word> was increased to 60 and our voting power increased to 12:

<code-word>lostVotingPower<code-word> is the return value of <code-word>getTokenVotingPower<code-word> and in our example it’s 12. After it, the contract tries to decrease the <code-word>votesFromOwnedTokens<code-word> and <code-word>tokenVotingPower<code-word>, but as you remember, the voting power saved there is 10. Hence, the <code-word>unstake<code-word> function will revert and not allow the user to withdraw, until the <code-word>monsterMultiplier<code-word> is not changed back to 50 or lower.

The mitigation is quite simple: add a mapping that stores original voting power of the specific <code-word>tokenId<code-word>. Also, you can read the finding here.

Conclusion

There are lots of different DAO and Governance attack vectors and vulnerabilities that are dependant on each protocol’s logic. But, if you’re planning to audit a DAO smart contract, don’t forget to check the common attack vectors we’ve listed above, they may sounds quite easy, but you’ll be surprised how often they’ve showed up in the past. Happy hunting!