Yield Protocol Logic Error Bugfix Review

Summary
On April 28th, the whitehat Paludo0x responsibly disclosed a critical logic error vulnerability to Yield Protocol via Immunefi. The vulnerability involved a potential exploit that could allow an attacker to drain the base tokens from a pool by manipulating the token balance of a contract.
Fortunately, thanks to the whitehat’s discovery and report via Immunefi, the Yield Protocol team was able to quickly remediate the issue, saving approximately $950k in funds across various pools on both the Arbitrum and Ethereum blockchains.
Thanks to the quick actions of both the whitehat and the protocol, no user funds were lost. Whitehat Paludo0x was awarded a bounty of $95,000 USDC for their discovery, which is equivalent to 10% of the funds at risk.
What is Yield Protocol?
Yield Protocol is a DeFi protocol that enables fixed-rate, fixed-term loan options between borrowers and lenders.
The protocol facilitates these transactions via fyTokens (fixed yield tokens), a type of ERC-20 token that can be exchanged one-to-one for an underlying asset upon reaching a predetermined maturity date.
Vulnerability Analysis
The whitehat reported a vulnerability that is associated with the strategy contract of the protocol, which enables liquidity providers to deposit combined liquidity to a YieldSpace Pool.
By depositing funds into the strategy contract, liquidity providers can mint strategy tokens. The amount of strategy tokens they receive corresponds proportionately to their deposit amount, allowing them to burn and redeem the LP tokens as well as any gain in fees/interest later.
This vulnerability is associated with the burning shares functionality burn(address to)
of the strategy contract which is responsible for burning the strategy tokens and allowing the user to withdraw the LP tokens.
/// @dev Burn strategy tokens to withdraw pool tokens. It can be called only when invested.
/// @param to Recipient for the pool tokens
/// @return poolTokensObtained Amount of pool tokens obtained
/// @notice The strategy tokens that the user burns need to have been transferred previously, using a batchable router.
function burn(address to) external isState(State.INVESTED)
returns (uint256 poolTokensObtained)
{
// Caching
IPool pool_ = pool;
uint256 poolCached_ = poolCached;
uint256 totalSupply_ = _totalSupply;
// Burn strategy tokens
uint256 burnt = _balanceOf[address(this)];
_burn(address(this), burnt);
poolTokensObtained = pool.balanceOf(address(this)) * burnt / totalSupply_;
pool_.safeTransfer(address(to), poolTokensObtained);
// Update pool cache
poolCached = poolCached_ - poolTokensObtained;
}
Initially, the contract’s strategy tokens are burned. Then, the amount of liquidity pool tokens to be acquired are calculated based on the LP tokens contained in the strategy contract, and are transferred to the designated address.
The pool tokens to be returned to the caller are calculated by:
poolTokensObtained = pool.balanceOf(address(this)) * burnt / totalSupply_;
This calculation is based on the LP tokens balance of the current strategy contract which could be inflated by sending the pool tokens directly to it.
The attacker has control over pool.balanceOf(address(this)), which allows them to inflate the pool tokens returned, by transferring a specific amount of pool tokens directly to the strategy contract before burning the strategy shares tokens.
As the pool tokens remain within the Strategy contract, the attacker making the call can mint the share tokens and then burn them back to retrieve the pool tokens that were utilized to inflate the calculation.
This is done by the following method:
- The attacker then call
mint(…)
to get the strategy share tokens. - The attacker then calls
burn(…)
again to get back the LP tokens that were transferred.
Proof of Concept (PoC):
The Immunefi team has prepared the following PoC for readers to see and test the vulnerability for themselves in Forge:
The steps to use this PoC is as follows:
- Install the Foundry framework from https://github.com/foundry-rs/foundry
- Clone the Immunefi bugfix review repository:
git clone
https://github.com/immunefi-team/bugfix-reviews-pocs.git
- Run the following command to run the PoC:
forge test -vvv — match-path ./test/YieldProtocol/AttackTest.t.sol
Log output of the POC:
Vulnerability Fix
In the patch, the project team modified the burn(…)
function in the strategy contract to use poolCached_
instead of pool.balanceOf(address(this))
to determine the quantity of LP tokens that should be transferred to the caller, fixing the issue.
The new formula would be:
poolTokensObtained = poolCached_ * burnt / totalSupply_;
You can view the Github commit of the fix here.
Acknowledgements
We would like to thank the whitehat Paludo0x for doing an amazing job in responsibly disclosing such an important bug. Big props also to the Yield Protocol team who quickly responded to the report and patched the issue.
If you’re a developer or a whitehat considering a lucrative bug-hunting career in web3 — this message is for you. With 10–100x the rewards commonly found in web2, your efforts will pay off exponentially by switching to web3.
Check out the Web3 Security Library, and start earning rewards on Immunefi — the leading bug bounty platform for web3 with the world’s biggest payouts.