Platform: Code4rena
Start Date: 15/06/2022
Pot Size: $30,000 USDC
Total HM: 5
Participants: 55
Period: 3 days
Judge: Jack the Pug
Id: 138
League: ETH
Rank: 2/55
Findings: 4
Award: $3,884.05
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: unforgiven
Also found by: GimelSec
3546.4894 USDC - $3,546.49
https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L421-L425 https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L107-L113 https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L411 https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L290
In _sendTokenToBribesProcessor()
, it sends tokens to bribesProcessor
. It seems to be ok because claimBribesFromHiddenHand()
will confirm that bribesProcessor
is not address(0)
. sweepRewardToken()
also triggers _sendTokenToBribesProcessor()
. But it doesn’t check bribesProcessor
’s address. Which could cause permanent loss of reward tokens if bribesProcessor
hasn't been set.
In _sendTokenToBribesProcessor()
, it sends tokens to bribesProcessor
without any check.
https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L421-L425
function _sendTokenToBribesProcessor(address token, uint256 amount) internal { // TODO: Too many SLOADs IERC20Upgradeable(token).safeTransfer(address(bribesProcessor), amount); emit RewardsCollected(token, amount); }
claimBribesFromHiddenHand()
will confirm that bribesProcessor
is not address(0)
https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L290
function claimBribesFromHiddenHand(IRewardDistributor hiddenHandDistributor, IRewardDistributor.Claim[] calldata _claims) external nonReentrant { _onlyGovernanceOrStrategist(); require(address(bribesProcessor) != address(0), "Bribes processor not set"); … }
But sweepRewardToken()
doesn’t check bribesProcessor
’s address
https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L107-L113
function sweepRewardToken(address token) public nonReentrant { _onlyGovernanceOrStrategist(); _onlyNotProtectedTokens(token); uint256 toSend = IERC20Upgradeable(token).balanceOf(address(this)); _handleRewardTransfer(token, toSend); }
None
Add check in _sendTokenToBribesProcessor()
function _sendTokenToBribesProcessor(address token, uint256 amount) internal { // TODO: Too many SLOADs require(address(bribesProcessor) != address(0), "Bribes processor not set"); IERC20Upgradeable(token).safeTransfer(address(bribesProcessor), amount); emit RewardsCollected(token, amount); }
or sweepRewardToken()
function sweepRewardToken(address token) public nonReentrant { _onlyGovernanceOrStrategist(); _onlyNotProtectedTokens(token); require(address(bribesProcessor) != address(0), "Bribes processor not set"); uint256 toSend = IERC20Upgradeable(token).balanceOf(address(this)); _handleRewardTransfer(token, toSend); }
#0 - GalloDaSballo
2022-06-18T23:23:04Z
Dup of #18
🌟 Selected for report: Picodes
Also found by: 0x1f8b, 0x52, Chom, GimelSec, IllIllI, berndartmueller, cccz, defsec, georgypetrov, hyh, kenzo, minhquanym, oyc_109, scaraven, unforgiven
50.7077 USDC - $50.71
https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L249 https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L257-L263 https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L275
In the above example code, we set our token_BAL limit to 0, which means we are willing to accept 100% slippage on our trade. That is generally a very bad idea
It lacks slippage control when calling BALANCER_VAULT.swap
, making it suffer from 100% slippage and front-running attack.
The third parameter of BALANCER_VAULT.swap
is 0:
https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L249
uint256 balEthBptEarned = BALANCER_VAULT.swap(singleSwap, fundManagement, 0, type(uint256).max);
https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L275
harvested[0].amount = BALANCER_VAULT.swap(singleSwap, fundManagement, 0, type(uint256).max);
And the minAmountsOut
is empty:
https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L257-L263
IBalancerVault.ExitPoolRequest memory exitPoolRequest = IBalancerVault.ExitPoolRequest({ assets: assets, minAmountsOut: new uint256[](2), userData: abi.encode(ExitKind.EXACT_BPT_IN_FOR_ONE_TOKEN_OUT, balEthBptEarned, BPT_WETH_INDEX), toInternalBalance: false }); BALANCER_VAULT.exitPool(BAL_ETH_POOL_ID, address(this), payable(address(this)), exitPoolRequest);
None
Consider setting a limit
value for BALANCER_VAULT.swap
and minAmountsOut
.
A good practice would be to user queryExit in BalancerHelpers to find the current amounts of tokens you would get for your BPT, and then account for some possible slippage.
#0 - GalloDaSballo
2022-06-18T23:13:05Z
We use flashbots, advice is still vulnerable. Because of leak of value I disagree with High
#1 - KenzoAgada
2022-06-23T02:30:39Z
Duplicate of #155
🌟 Selected for report: scaraven
Also found by: GimelSec, berndartmueller, cccz, dipp, kenzo, kirk-baird, unforgiven
235.5937 USDC - $235.59
https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L428-L431 https://github.com/Badger-Finance/badger-vaults-1.5/blob/0.1.0/contracts/BaseStrategy.sol#L346-L353
_sendBadgerToTree()
sends amount
BADGER to BADGER_TREE. However _processExtraToken()
in _sendBadgerToTree()
also sends amount
BADGER to the vault. it sends amount
BADGER twice. It could result in revert if it doesn't have enough balance of BADGER.
_sendBadgerToTree()
sends amount
BADGER to BADGER_TREE
https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L428-L431
function _sendBadgerToTree(uint256 amount) internal { IERC20Upgradeable(BADGER).safeTransfer(BADGER_TREE, amount); _processExtraToken(address(BADGER), amount); }
_processExtraToken()
in _sendBadgerToTree()
also sends amount
BADGER to the vault
https://github.com/Badger-Finance/badger-vaults-1.5/blob/0.1.0/contracts/BaseStrategy.sol#L346-L353
function _processExtraToken(address _token, uint256 _amount) internal { require(_token != want, "Not want, use _reportToVault"); require(_token != address(0), "Address 0"); require(_amount != 0, "Amount 0"); IERC20Upgradeable(_token).safeTransfer(vault, _amount); IVault(vault).reportAdditionalToken(_token); }
None
Removing _processExtraToken()
in _sendBadgerToTree()
can fix it.
But I’m not sure if _processExtraToken()
is necessary in _sendBadgerToTree()
. If so, _processExtraToken()
should be rewrite in MyStrategy.sol
to prevent sending BADGER twice.
#0 - KenzoAgada
2022-06-21T13:05:33Z
Duplicate of #2
🌟 Selected for report: IllIllI
Also found by: 0v3rf10w, 0x1f8b, 0xDjango, 0xFar5eer, 0xNazgul, 0xNineDec, 242, Chom, Czar102, Funen, GimelSec, Meera, Picodes, Sm4rty, Tadashi, TerrierLover, Waze, _Adam, a12jmx, asutorufos, codexploder, cryptphi, defsec, gzeon, hyh, joestakey, minhquanym, oyc_109, reassor, robee, saian, sorrynotsorry, unforgiven, zzzitron
51.2645 USDC - $51.26
https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L98-L101 https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L107-L113 https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L421-L425
bribesProcessor
is used to receive bribes and reward tokens. However there is almost no check for newBribesProcessor
in setBribesProcessor
. Malicious governance can set any address as bribesProcessor
. Malicious governance could set an EOA as bribesProcessor
.
There is almost no check for newBribesProcessor
in setBribesProcessor
.
https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L98-L101
///@dev Change the contract that handles bribes function setBribesProcessor(IBribesProcessor newBribesProcessor) external { _onlyGovernance(); bribesProcessor = newBribesProcessor; }
After setting a malicious bribesProcessor
. Malicious governance can call sweepRewardToken
to steal rewards that are not protected.
https://github.com/Badger-Finance/vested-aura/blob/v0.0.2/contracts/MyStrategy.sol#L107-L113
function sweepRewardToken(address token) public nonReentrant { _onlyGovernanceOrStrategist(); _onlyNotProtectedTokens(token); uint256 toSend = IERC20Upgradeable(token).balanceOf(address(this)); _handleRewardTransfer(token, toSend); }
None
Add a check in setBribesProcessor
to make sure that newBribesProcessor
is a contract.
#0 - GalloDaSballo
2022-06-18T23:15:55Z
Because the bribesProcessor
can be set, there are no guarantees in the code that the bribesProcessor
will not be malicious, any check would be an illusion as malicious governance would be able to also fabricate the contract.
That said the bribesProcessor
will have the vault address hardcoded to only work with it, guaranteeing that once bribes are received they will be properly processed.
Don't have a strong opinion on the validity of this finding as ultimately yet, the fact that governance can change the bribes processor address does open to the ability of stealing yield.
At the same time the finding is lazy and doesn't offer a solution that is actionable