Closed code423n4 closed 1 year ago
dmvt marked the issue as primary issue
Looking at a current vault like https://etherscan.io/address/0x27B5739e22ad9033bcBf192059122d163b60349D We see that totalAssets gets calculated like this:
def _totalAssets() -> uint256:
# See note on `totalAssets()`.
return self.token.balanceOf(self) + self.totalDebt
Which matches the implementation
RedVeil marked the issue as sponsor disputed
RedVeil marked the issue as disagree with severity
dmvt marked issue #728 as primary and marked this issue as a duplicate of 728
dmvt changed the severity to 2 (Med Risk)
dmvt marked the issue as partial-50
Hi @dmvt,
Comparing to #728, this report and #728 both describe the scenario that the return value of the YearnAdapter._yTotalAssets
function can be inflated because Yearn vault's self.totalIdle
accounting variable is not used. Both reports also mention about Yearn's Vault.sweep
function that can be performed by Yearn's governance. Thus, the information and content of both reports are very similar. Yet, this report is rated a partial-50
instead of a full score. Hence, I would like to ask about the reason behind this.
Thanks again for your work and time!
dmvt marked the issue as full credit
dmvt marked the issue as satisfactory
Hi @dmvt,
Comparing to #728, this report and #728 both describe the scenario that the return value of the
YearnAdapter._yTotalAssets
function can be inflated because Yearn vault'sself.totalIdle
accounting variable is not used. Both reports also mention about Yearn'sVault.sweep
function that can be performed by Yearn's governance. Thus, the information and content of both reports are very similar. Yet, this report is rated apartial-50
instead of a full score. Hence, I would like to ask about the reason behind this.Thanks again for your work and time!
You're correct. My mistake. Full credit restored.
Lines of code
https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/adapter/yearn/YearnAdapter.sol#L80-L82 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/adapter/yearn/YearnAdapter.sol#L89-L98 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/adapter/yearn/YearnAdapter.sol#L101-L103 https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/adapter/yearn/YearnAdapter.sol#L109-L111 https://github.com/yearn/yearn-vaults/blob/master/contracts/Vault.vy#L809-L813 https://github.com/yearn/yearn-vaults/blob/master/contracts/Vault.vy#L1811-L1838
Vulnerability details
Impact
The
YearnAdapter._yTotalAssets
function below attempts to resemble Yearn'sVault._totalAssets
function but usesIERC20(asset()).balanceOf(address(yVault))
as part of the total quantity of all assets under control of the Yearn vault. In contrast, Yearn'sVault._totalAssets
function below uses theself.totalIdle
accounting variable instead of the asset balance owned by the vault returned by the asset token'sbalanceOf
function. As shown by Yearn'sVault.sweep
function below, it is possible that extra asset token amount is sent to the Yearn vault by accident in which the governance can withdraw such extra amount from the vault.When such extra asset token amount is transferred to the Yearn vault, the
YearnAdapter._yTotalAssets
function will return a value that is bigger than it should be becauseIERC20(asset()).balanceOf(address(yVault))
is bigger than the Yearn vault'sself.totalIdle
at that moment. This will causeYearnAdapter._totalAssets()
to be higher than it should be, which will inflateAdapterBase.totalAssets()
andVault.totalAssets()
for the vault that uses theYearnAdapter
contract as its adapter. Hence, comparing to if Yearn vault'sself.totalIdle
is used in theYearnAdapter._yTotalAssets
function, calling theVault.deposit
function will mint less shares for depositing a given asset amount, and calling theVault.mint
function will deposit more asset amount for minting a given number of shares. If Yearn vault's governance has withdrawn the extra asset token amount before theVault.withdraw
orVault.redeem
function is called, calling theVault.withdraw
orVault.redeem
function will transfer an asset amount that is less than the deposited amount to the user in this case. As a result, the user loses the difference between the deposited and withdrawn asset amounts.https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/adapter/yearn/YearnAdapter.sol#L80-L82
https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/adapter/yearn/YearnAdapter.sol#L89-L98
https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/adapter/yearn/YearnAdapter.sol#L101-L103
https://github.com/code-423n4/2023-01-popcorn/blob/main/src/vault/adapter/yearn/YearnAdapter.sol#L109-L111
https://github.com/yearn/yearn-vaults/blob/master/contracts/Vault.vy#L809-L813
https://github.com/yearn/yearn-vaults/blob/master/contracts/Vault.vy#L1811-L1838
Proof of Concept
The following steps can occur for the described scenario.
YearnAdapter
contract as its adapter exists.IERC20(asset()).balanceOf(address(yVault))
is now bigger than the Yearn vault'sself.totalIdle
.Vault.mint
function to mint 1 share, which deposits 10e18 asset tokens to the Yearn vault.sweep
function to withdraw the extra asset token amount from the Yearn vault.Vault.redeem
function to redeem 1 share but only 9e18 asset tokens are withdrawn. Hence, the user loses 1e18 asset tokens.Tools Used
VSCode
Recommended Mitigation Steps
The
YearnAdapter._yTotalAssets
function can be updated to use the Yearn vault'sself.totalIdle
instead ofIERC20(asset()).balanceOf(address(yVault))
.