Resources

People often ask me "How did you learn how to hack?" The answer: by reading. This page is a collection of the blog posts and other articles that I have accumulated over the years of my journey. Enjoy!

Strategy v2 Burn Bug Post Mortem- 1190

Alberto Cuesta CanadaPosted 2 Years Ago
  • The Yield Protocol is a fixed-rate borrowing and lending protocol on Ethereum. As demonstrated by the name "Yield", getting yield from the assets provided is an extremely important part of this protocol.
  • With ERC20 tokens liquidity provider tokens, the mint() and burn() functions are common for adding liquidity and removing it from the protocol. mint() will create LP tokens from the provided asset token provided. burn() will destroy the LP token and give back the original asset token. These tokens are used for portions of the pool rewards.
  • In the burn() function, all of the users tokens are burned. Then, based upon the amount of tokens burned and their share in the pool, it will give them the underlying token back. The code for this is below:
    uint256 burnt = _balanceOf[address(this)];
    _burn(address(this), burnt); 
    
    poolTokensObtained = 
      pool.balanceOf(address(this)) * 
      burnt / 
      totalSupply_;
    
  • The attacker can donate a large set of tokens to inflate the balance of the pool. This leads the pool to the pool sending more tokens to a user than they should. Crazily enough, these donated funds are not lost though! An attacker can call mint(), which will use the difference between the balance and the cached pool. So, the inflation of the amount of tokens being sent to the attacker doesn't cost them anything.
  • The article has some interesting insights into the development process. First, cache-based contracts are known to be vulnerable to donation attacks if the developer is not careful. The author mentions going through the YieldSpace-TV project and validating that every single use of balanceOf() was not vulnerable.
  • This new feature was audited and missed. However, they mention that the level of complexity of this bug warranted a code based audit. The time pressure led to an internal audit instead. In this case, the bug bounty program saved the day, which is amazing! Having multiple levels like this prevents major hacks from happening.
  • Once the vulnerability was discovered, the protocol decided to use the eject() function to take all of the funds. They learned a few lessons from this warroom. First, having a pause() would have allowed them to explore their options without an attack being viable. Second, the contract is not upgradable but uses the code>eject() functionality to recover funds. By having the ability to upgrade contracts, restoring the protocol would have been much easier.
  • Overall, an amazing post into the world of bug bounties, handling issues and protocol design. The fix is a single line change to use the cached version instead of the balance of the pool.