The purpose of this new soil issue mechanism is to minimize the potential of over-issuing Soil (debt) when Beanstalk is below peg. By comparing both the time-weighted average (TWA) deltaB and the instantaneous deltaB (from instantaneous reserves), the system can choose the more conservative value, thus reducing potential manipulation and stabilizing the protocol.
As we can see below, the protocol will always fetch the min price, given the scenario above it will always fetch the twaDeltaB.
The PoC below proves that Beanstalk will always fetch the twaDeltaB even when the correct price should be from the instantaneous reserve.
const { expect } = require('chai')
const { deploy } = require('../scripts/deploy.js')
const { takeSnapshot, revertToSnapshot } = require("./utils/snapshot")
const { to6, toStalk, toBean, to18 } = require('./utils/helpers.js');
const { USDC, UNRIPE_BEAN, UNRIPE_LP, BEAN,ETH_USDC_UNISWAP_V3, BASE_FEE_CONTRACT, THREE_CURVE, THREE_POOL, BEAN_3_CURVE, BEAN_ETH_WELL, WSTETH, WETH, BEAN_WSTETH_WELL } = require('./utils/constants.js');
const { ethers } = require('hardhat');
const { setEthUsdChainlinkPrice, setWstethUsdPrice } = require('../utils/oracle.js');
const { deployBasin } = require('../scripts/basin.js');
const ZERO_BYTES = ethers.utils.formatBytes32String('0x0')
const { deployBasinV1_1Upgrade } = require('../scripts/basinV1_1.js');
const { advanceTime } = require('../utils/helpers.js');
const { setReserves } = require('../utils/well.js');
let user, user2, owner;
let userAddress, ownerAddress, user2Address;
describe('SunSoilBroken', function () {
before(async function () {
[owner, user, user2] = await ethers.getSigners()
userAddress = user.address;
user2Address = user2.address;
const contracts = await deploy("Test", false, true)
ownerAddress = contracts.account;
this.diamond = contracts.beanstalkDiamond;
this.season = await ethers.getContractAt('MockSeasonFacet', this.diamond.address)
this.fertilizer = await ethers.getContractAt('MockFertilizerFacet', this.diamond.address)
this.silo = await ethers.getContractAt('MockSiloFacet', this.diamond.address)
this.field = await ethers.getContractAt('MockFieldFacet', this.diamond.address)
this.usdc = await ethers.getContractAt('MockToken', USDC);
this.wsteth = await ethers.getContractAt('MockToken', WSTETH);
this.unripe = await ethers.getContractAt('MockUnripeFacet', this.diamond.address)
this.basefee = await ethers.getContractAt('MockBlockBasefee', BASE_FEE_CONTRACT);
this.tokenFacet = await ethers.getContractAt('TokenFacet', contracts.beanstalkDiamond.address)
this.bean = await ethers.getContractAt('MockToken', BEAN);
this.threeCurve = await ethers.getContractAt('MockToken', THREE_CURVE);
this.threePool = await ethers.getContractAt('Mock3Curve', THREE_POOL);
await this.threePool.set_virtual_price(to18('1'));
this.beanThreeCurve = await ethers.getContractAt('MockMeta3Curve', BEAN_3_CURVE);
this.uniswapV3EthUsdc = await ethers.getContractAt('MockUniswapV3Pool', ETH_USDC_UNISWAP_V3);
this.siloGetters = await ethers.getContractAt('SiloGettersFacet', this.diamond.address)
await this.beanThreeCurve.set_supply(toBean('100000'));
await this.beanThreeCurve.set_A_precise('1000');
await this.beanThreeCurve.set_virtual_price(to18('1'));
await this.beanThreeCurve.set_balances([toBean('10000'), to18('10000')]);
await this.beanThreeCurve.reset_cumulative();
this.whitelist = await ethers.getContractAt('WhitelistFacet', this.diamond.address);
this.result = await this.whitelist.connect(owner).dewhitelistToken(BEAN_3_CURVE);
await this.usdc.mint(owner.address, to6('10000'))
await this.bean.mint(owner.address, to6('10000'))
await this.wsteth.mint(owner.address, to18('10000'))
await this.usdc.connect(owner).approve(this.diamond.address, to6('10000'))
await this.wsteth.connect(owner).approve(this.diamond.address, to18('10000'))
this.unripeBean = await ethers.getContractAt('MockToken', UNRIPE_BEAN)
this.unripeLP = await ethers.getContractAt('MockToken', UNRIPE_LP)
await this.unripeLP.mint(userAddress, to6('1000'))
await this.unripeLP.connect(user).approve(this.diamond.address, to6('100000000'))
await this.unripeBean.mint(userAddress, to6('1000'))
await this.unripeBean.connect(user).approve(this.diamond.address, to6('100000000'))
await this.unripe.addUnripeToken(UNRIPE_BEAN, BEAN, ZERO_BYTES)
await this.unripe.addUnripeToken(UNRIPE_LP, BEAN_WSTETH_WELL, ZERO_BYTES);
await setEthUsdChainlinkPrice('1000');
await setWstethUsdPrice('1000');
let c = await deployBasin(true, undefined, false, true)
await c.multiFlowPump.update([toBean('10000'), to18('10')], 0x00);
await c.multiFlowPump.update([toBean('10000'), to18('10')], 0x00);
c = await deployBasinV1_1Upgrade(c, true, undefined, true, justDeploy=true, mockPump=false)
this.pump = c.multiFlowPump;
this.well = c.well;
this.weth = await ethers.getContractAt('MockToken', WSTETH);
await this.bean.mint(userAddress, toBean('10000000000'));
await this.bean.mint(user2Address, toBean('10000000000'));
await this.weth.mint(userAddress, to18('1000000000'));
await this.weth.mint(user2Address, to18('1000000000'));
await this.bean.connect(user).approve(this.well.address, ethers.constants.MaxUint256);
await this.bean.connect(user2).approve(this.well.address, ethers.constants.MaxUint256);
await this.bean.connect(owner).approve(this.well.address, ethers.constants.MaxUint256);
await this.weth.connect(user).approve(this.well.address, ethers.constants.MaxUint256);
await this.weth.connect(user2).approve(this.well.address, ethers.constants.MaxUint256);
await this.weth.connect(owner).approve(this.well.address, ethers.constants.MaxUint256);
await this.season.siloSunrise(0)
})
beforeEach(async function () {
snapshotId = await takeSnapshot()
})
afterEach(async function () {
await revertToSnapshot(snapshotId)
})
it("When deltaB < 0 logic for -twaDeltaB and -instantaneous deltaB (-twaDeltaB < -instDeltaB) is broken", async function () {
await this.silo.mockWhitelistToken(BEAN_WSTETH_WELL, this.silo.interface.getSighash("mockBDV(uint256 amount)"), "10000", "1");
await setReserves(
user,
this.well,
[to6('10000'), to18('10')]
);
await setReserves(
user,
this.well,
[to6('10000'), to18('10')]
);
await advanceTime(1800)
await setReserves(
user,
this.well,
[to6('2000000'), to18('1000')]
);
await setReserves(
user,
this.well,
[to6('2000000'), to18('1000')]
);
await advanceTime(1800)
await user.sendTransaction({
to: this.diamond.address,
value: 0
})
await setReserves(
user,
this.well,
[to6('3000000'), to18('1000')]
);
this.result = await this.season.sunSunrise('-100000000', 8);
await expect(this.result).to.emit(this.season, 'Soil').withArgs(3, '100000000');
await expect(await this.field.totalSoil()).to.be.equal('100000000');
await advanceTime(1800)
await setReserves(
user,
this.well,
[to6('3100000'), to18('1000')]
);
let previousInstantaneousDeltaB = -639582614561
this.result = await this.season.sunSunrise(previousInstantaneousDeltaB, 8);
await expect(this.result).to.emit(this.season, 'Soil').withArgs(4, '585786437627');
await expect(await this.field.totalSoil()).to.be.equal('585786437627');
})
})