Skip to content

Commit

Permalink
EBIP-8: Enroot BDV Rounding (#389)
Browse files Browse the repository at this point in the history
  • Loading branch information
publiuss committed May 14, 2023
2 parents 763acf0 + c6425b4 commit 75ad38f
Show file tree
Hide file tree
Showing 10 changed files with 6,496 additions and 2,746 deletions.
8,523 changes: 6,019 additions & 2,504 deletions protocol/abi/Beanstalk.json

Large diffs are not rendered by default.

21 changes: 11 additions & 10 deletions protocol/contracts/beanstalk/silo/SiloFacet/SiloFacet.sol
Expand Up @@ -341,43 +341,44 @@ contract SiloFacet is TokenSilo {
address token,
uint32[] calldata seasons,
uint256[] calldata amounts
) external nonReentrant updateSilo {
) external payable nonReentrant updateSilo {
require(s.u[token].underlyingToken != address(0), "Silo: token not unripe");

// First, remove Deposits because every deposit is in a different season, we need to get the total Stalk/Seeds, not just BDV
AssetsRemoved memory ar = removeDeposits(msg.sender, token, seasons, amounts);

AssetsAdded memory aa;
// Get new BDV and calculate Seeds (Seeds are not Season dependent like Stalk)
uint256 newBDV = LibTokenSilo.beanDenominatedValue(token, ar.tokensRemoved);
uint256 newStalk;
aa.bdvAdded = LibTokenSilo.beanDenominatedValue(token, ar.tokensRemoved);

// Iterate through all seasons, redeposit the tokens with new BDV and summate new Stalk.
for (uint256 i; i < seasons.length; ++i) {
uint256 bdv = amounts[i].mul(newBDV).div(ar.tokensRemoved); // Cheaper than calling the BDV function multiple times.
uint256 bdv = amounts[i].mul(aa.bdvAdded).div(ar.tokensRemoved); // Cheaper than calling the BDV function multiple times.
LibTokenSilo.addDeposit(
msg.sender,
token,
seasons[i],
amounts[i],
bdv
);
newStalk = newStalk.add(
aa.stalkAdded = aa.stalkAdded.add(
bdv.mul(s.ss[token].stalk).add(
LibSilo.stalkReward(
bdv.mul(s.ss[token].seeds),
season() - seasons[i]
)
)
);
// Count BDV to prevent a rounding error. Do multiplication later to save gas.
aa.seedsAdded = aa.seedsAdded.add(bdv);
}

uint256 newSeeds = newBDV.mul(s.ss[token].seeds);
aa.seedsAdded = aa.seedsAdded.mul(s.ss[token].seeds);

// Add new Stalk
LibSilo.depositSiloAssets(
msg.sender,
newSeeds.sub(ar.seedsRemoved),
newStalk.sub(ar.stalkRemoved)
aa.seedsAdded.sub(ar.seedsRemoved),
aa.stalkAdded.sub(ar.stalkRemoved)
);
}

Expand All @@ -391,7 +392,7 @@ contract SiloFacet is TokenSilo {
address token,
uint32 _season,
uint256 amount
) external nonReentrant updateSilo {
) external payable nonReentrant updateSilo {
require(s.u[token].underlyingToken != address(0), "Silo: token not unripe");

// First, remove Deposit and Redeposit with new BDV
Expand Down
6 changes: 6 additions & 0 deletions protocol/contracts/beanstalk/silo/SiloFacet/TokenSilo.sol
Expand Up @@ -71,6 +71,12 @@ contract TokenSilo is Silo {
uint256 bdvRemoved;
}

struct AssetsAdded {
uint256 stalkAdded;
uint256 seedsAdded;
uint256 bdvAdded;
}

/**
* Getters
**/
Expand Down
44 changes: 19 additions & 25 deletions protocol/contracts/libraries/Silo/LibTokenSilo.sol
Expand Up @@ -96,13 +96,25 @@ library LibTokenSilo {
s.a[account].deposits[token][id].amount,
s.a[account].deposits[token][id].bdv
);

// If amount to remove is greater than the amount in the Deposit, migrate legacy Deposit to new Deposit
if (amount > crateAmount) {
// If Unripe Deposit, fetch whole Deposit balance and delete legacy deposit references.
if (LibUnripeSilo.isUnripeBean(token)) {
(crateAmount, crateBDV) = LibUnripeSilo.unripeBeanDeposit(account, id);
LibUnripeSilo.removeUnripeBeanDeposit(account, id);
} else if (LibUnripeSilo.isUnripeLP(token)) {
(crateAmount, crateBDV) = LibUnripeSilo.unripeLPDeposit(account, id);
LibUnripeSilo.removeUnripeLPDeposit(account, id);
}
require(crateAmount >= amount, "Silo: Crate balance too low.");
}

// Partial Withdraw
if (amount < crateAmount) {
uint256 base = amount.mul(crateBDV).div(crateAmount);
uint256 newBase = uint256(s.a[account].deposits[token][id].bdv).sub(
base
);
uint256 newAmount = uint256(s.a[account].deposits[token][id].amount)
.sub(amount);
uint256 newBase = crateBDV.sub(base);
uint256 newAmount = crateAmount.sub(amount);
require(
newBase <= uint128(-1) && newAmount <= uint128(-1),
"Silo: uint128 overflow."
Expand All @@ -112,26 +124,8 @@ library LibTokenSilo {
return base;
}

if (crateAmount > 0) delete s.a[account].deposits[token][id];

if (amount > crateAmount) {
amount -= crateAmount;
if (LibUnripeSilo.isUnripeBean(token))
return
crateBDV.add(
LibUnripeSilo.removeUnripeBeanDeposit(
account,
id,
amount
)
);
else if (LibUnripeSilo.isUnripeLP(token))
return
crateBDV.add(
LibUnripeSilo.removeUnripeLPDeposit(account, id, amount)
);
revert("Silo: Crate balance too low.");
}
// Full Withdraw
delete s.a[account].deposits[token][id];
}

/*
Expand Down
101 changes: 20 additions & 81 deletions protocol/contracts/libraries/Silo/LibUnripeSilo.sol
Expand Up @@ -20,31 +20,25 @@ library LibUnripeSilo {
uint256 private constant AMOUNT_TO_BDV_BEAN_3CRV = 992035;
uint256 private constant AMOUNT_TO_BDV_BEAN_LUSD = 983108;

/**
* @dev Deletes the legacy Bean storage reference for a given `account` and `id`.
*/
function removeUnripeBeanDeposit(
address account,
uint32 id,
uint256 amount
) internal returns (uint256 bdv) {
_removeUnripeBeanDeposit(account, id, amount);
bdv = amount.mul(C.initialRecap()).div(1e18);
}

function _removeUnripeBeanDeposit(
address account,
uint32 id,
uint256 amount
) private {
uint32 id
) internal {
AppStorage storage s = LibAppStorage.diamondStorage();
s.a[account].bean.deposits[id] = s.a[account].bean.deposits[id].sub(
amount,
"Silo: Crate balance too low."
);
delete s.a[account].bean.deposits[id];
}

function isUnripeBean(address token) internal pure returns (bool b) {
b = token == C.UNRIPE_BEAN;
}

/**
* @dev Returns the whole Unripe Bean Deposit for a given `account` and `season`.
* Includes non-legacy balance.
*/
function unripeBeanDeposit(address account, uint32 season)
internal
view
Expand All @@ -59,83 +53,28 @@ library LibUnripeSilo {
.add(legacyAmount.mul(C.initialRecap()).div(1e18));
}

/**
* @dev Deletes all legacy LP storage references for a given `account` and `id`.
*/
function removeUnripeLPDeposit(
address account,
uint32 id,
uint256 amount
) internal returns (uint256 bdv) {
bdv = _removeUnripeLPDeposit(account, id, amount);
bdv = bdv.mul(C.initialRecap()).div(1e18);
}

function _removeUnripeLPDeposit(
address account,
uint32 id,
uint256 amount
) private returns (uint256 bdv) {
uint32 id
) internal {
AppStorage storage s = LibAppStorage.diamondStorage();
(uint256 amount1, uint256 bdv1) = getBeanEthUnripeLP(account, id);
if (amount1 >= amount) {
uint256 removed = amount.mul(s.a[account].lp.deposits[id]).div(
amount1
);
s.a[account].lp.deposits[id] = s.a[account].lp.deposits[id].sub(
removed
);
removed = amount.mul(bdv1).div(amount1);
s.a[account].lp.depositSeeds[id] = s
.a[account]
.lp
.depositSeeds[id]
.sub(removed.mul(4));
return removed;
}
amount -= amount1;
bdv = bdv1;
delete s.a[account].lp.depositSeeds[id];
delete s.a[account].lp.deposits[id];

(amount1, bdv1) = getBean3CrvUnripeLP(account, id);
if (amount1 >= amount) {
Account.Deposit storage d = s.a[account].deposits[
C.unripeLPPool1()
][id];
uint128 removed = uint128(amount.mul(d.amount).div(amount1));
s.a[account].deposits[C.unripeLPPool1()][id].amount = d.amount.sub(
removed
);
removed = uint128(amount.mul(d.bdv).div(amount1));
s.a[account].deposits[C.unripeLPPool1()][id].bdv = d.bdv.sub(
removed
);
return bdv.add(removed);
}
amount -= amount1;
bdv = bdv.add(bdv1);
delete s.a[account].deposits[C.unripeLPPool1()][id];

(amount1, bdv1) = getBeanLusdUnripeLP(account, id);
if (amount1 >= amount) {
Account.Deposit storage d = s.a[account].deposits[
C.unripeLPPool2()
][id];
uint128 removed = uint128(amount.mul(d.amount).div(amount1));
s.a[account].deposits[C.unripeLPPool2()][id].amount = d.amount.sub(
removed
);
removed = uint128(amount.mul(d.bdv).div(amount1));
s.a[account].deposits[C.unripeLPPool2()][id].bdv = d.bdv.sub(
removed
);
return bdv.add(removed);
}
revert("Silo: Crate balance too low.");
delete s.a[account].deposits[C.unripeLPPool2()][id];
}

function isUnripeLP(address token) internal pure returns (bool b) {
b = token == C.UNRIPE_LP;
}

/**
* @dev Returns the whole Unripe LP Deposit for a given `account` and `season`.
* Includes non-legacy balance.
*/
function unripeLPDeposit(address account, uint32 season)
internal
view
Expand Down
1 change: 1 addition & 0 deletions protocol/package.json
Expand Up @@ -44,6 +44,7 @@
"solidity-coverage": "^0.8.2"
},
"dependencies": {
"@ethereum-waffle/chai": "4.0.10",
"@openzeppelin/contracts": "^3.4.0",
"@openzeppelin/contracts-upgradeable": "^3.4.0",
"@openzeppelin/contracts-upgradeable-8": "npm:@openzeppelin/contracts-upgradeable@^4.7.3",
Expand Down
25 changes: 24 additions & 1 deletion protocol/scripts/ebips.js
@@ -1,5 +1,7 @@
const fs = require('fs')
const { BEANSTALK } = require('../test/utils/constants')
const { getBeanstalk, impersonateBeanstalkOwner, mintEth, strDisplay } = require("../utils")
const { upgradeWithNewFacets } = require('./diamond')

async function ebip6(mock = true, account = undefined) {
if (account == undefined) {
Expand Down Expand Up @@ -51,6 +53,26 @@ async function ebip7(mock = true, account = undefined) {

}

async function ebip8(mock = true, account = undefined) {
if (account == undefined) {
account = await impersonateBeanstalkOwner()
await mintEth(account.address)
}

await upgradeWithNewFacets({
diamondAddress: BEANSTALK,
facetNames: [
'SiloFacet',
'ConvertFacet'
],
bip: false,
object: !mock,
verbose: true,
account: account
})
}


async function bipDiamondCut(name, dc, account, mock = true) {
beanstalk = await getBeanstalk()
if (mock) {
Expand All @@ -69,4 +91,5 @@ async function bipDiamondCut(name, dc, account, mock = true) {
}

exports.ebip6 = ebip6
exports.ebip7 = ebip7
exports.ebip7 = ebip7
exports.ebip8 = ebip8

0 comments on commit 75ad38f

Please sign in to comment.