Skip to content

Commit

Permalink
Merge pull request #24 from Blueberryfi/sherlock-audit-fix-117
Browse files Browse the repository at this point in the history
fix: issue#117 - Issue 290 from previous contest has not been fully addressed by fixes
  • Loading branch information
Gornutz authored May 18, 2023
2 parents eebdc53 + bf619fb commit dfdb119
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 4 deletions.
18 changes: 18 additions & 0 deletions abi/BlueBerryBank.json
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,11 @@
"name": "ORACLE_NOT_SUPPORT_WTOKEN",
"type": "error"
},
{
"inputs": [],
"name": "REPAY_ALLOW_NOT_WARMED_UP",
"type": "error"
},
{
"inputs": [
{
Expand Down Expand Up @@ -1632,6 +1637,19 @@
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "repayResumedTimestamp",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
Expand Down
16 changes: 15 additions & 1 deletion contracts/BlueBerryBank.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ contract BlueBerryBank is

uint256 public nextPositionId; // Next available position ID, starting from 1 (see initialize).
uint256 public bankStatus; // Each bit stores certain bank status, e.g. borrow allowed, repay allowed
uint256 public repayResumedTimestamp; // Timestamp that repay is allowed or resumed

address[] public allBanks; // The list of all listed banks.
mapping(address => Bank) public banks; // Mapping from token to bank data.
Expand Down Expand Up @@ -274,7 +275,14 @@ contract BlueBerryBank is
/// @dev Set bank status
/// @param _bankStatus new bank status to change to
function setBankStatus(uint256 _bankStatus) external onlyOwner {
bool repayAllowedStatusBefore = isRepayAllowed();
bankStatus = _bankStatus;
bool repayAllowedStatusAfter = isRepayAllowed();

// Update repayResumedTimestamp when repayAllowed status is changed from "off" to "on"
if (!repayAllowedStatusBefore && repayAllowedStatusAfter) {
repayResumedTimestamp = block.timestamp;
}
}

/// @dev Bank borrow status allowed or not
Expand Down Expand Up @@ -408,7 +416,7 @@ contract BlueBerryBank is
(address[] memory tokens, uint256[] memory rewards) = IERC20Wrapper(
pos.collToken
).pendingRewards(pos.collId, pos.collateralSize);
for (uint256 i; i < tokens.length; i++) {
for (uint256 i; i < tokens.length; i++) {
if (oracle.isTokenSupported(tokens[i])) {
rewardsValue += oracle.getTokenValue(tokens[i], rewards[i]);
}
Expand Down Expand Up @@ -501,6 +509,12 @@ contract BlueBerryBank is
if (pos.collToken == address(0))
revert Errors.BAD_COLLATERAL(positionId);

// Revert liquidation when repayAllowed was not warmed up
if (
block.timestamp <
repayResumedTimestamp + Constants.LIQUIDATION_REPAY_WARM_UP_PERIOD
) revert Errors.REPAY_ALLOW_NOT_WARMED_UP();

uint256 oldShare = pos.debtShare;
(uint256 amountPaid, uint256 share) = _repay(
positionId,
Expand Down
2 changes: 2 additions & 0 deletions contracts/utils/BlueBerryConst.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ uint32 constant MAX_DELAY_ON_SWAP = 2 hours;
uint32 constant SEQUENCE_GRACE_PERIOD_TIME = 3600;

uint256 constant CHAINLINK_PRICE_FEED_PRECISION = 1e8;

uint256 constant LIQUIDATION_REPAY_WARM_UP_PERIOD = 4 hours;
1 change: 1 addition & 0 deletions contracts/utils/BlueBerryErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ error REPAY_NOT_ALLOWED();
error WITHDRAW_LEND_NOT_ALLOWED();
error LOCKED();
error NOT_IN_EXEC();
error REPAY_ALLOW_NOT_WARMED_UP();

error DIFF_COL_EXIST(address collToken);
error NOT_LIQUIDATABLE(uint256 positionId);
Expand Down
51 changes: 48 additions & 3 deletions test/bank.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { solidity } from 'ethereum-waffle'
import { near } from './assertions/near'
import { roughlyNear } from './assertions/roughlyNear'
import { Protocol, setupIchiProtocol } from './helpers/setup-ichi-protocol';
import { evm_mine_blocks } from './helpers';
import { evm_mine_blocks, evm_increaseTime } from './helpers';
import { TickMath } from '@uniswap/v3-sdk';

chai.use(solidity)
Expand Down Expand Up @@ -552,7 +552,52 @@ describe('Bank', () => {
bank.connect(alice).liquidate(1, USDC, liqAmount)
).to.be.revertedWith("NOT_LIQUIDATABLE")
})
it("should be able to liquidate the position => (OV - PV)/CV = LT", async () => {
it("should revert when repayAllowed is not warmed up", async () => {
await evm_mine_blocks(10);
await ichiVault.rebalance(-260400, -260200, -260800, -260600, 0);
let positionInfo = await bank.getPositionInfo(positionId);
let debtValue = await bank.getDebtValue(positionId)
let positionValue = await bank.getPositionValue(positionId);
let risk = await bank.getPositionRisk(positionId)
console.log("Debt Value:", utils.formatUnits(debtValue));
console.log("Position Value:", utils.formatUnits(positionValue));
console.log('Position Risk:', utils.formatUnits(risk, 2), '%');
console.log("Position Size:", utils.formatUnits(positionInfo.collateralSize));

const pendingIchi = await ichiFarm.pendingIchi(ICHI_VAULT_PID, wichi.address)
console.log("Pending ICHI:", utils.formatUnits(pendingIchi, 9))
await ichiV1.transfer(ichiFarm.address, pendingIchi.mul(100))
await ichiFarm.updatePool(ICHI_VAULT_PID)

console.log('===ICHI token dumped from $5 to $0.1===');
await mockOracle.setPrice(
[ICHI],
[
BigNumber.from(10).pow(18).mul(1), // $0.5
]
);
positionInfo = await bank.getPositionInfo(positionId);
debtValue = await bank.getDebtValue(positionId)
positionValue = await bank.getPositionValue(positionId);
risk = await bank.getPositionRisk(positionId)
console.log("Cur Pos:", positionInfo);
console.log("Debt Value:", utils.formatUnits(debtValue));
console.log("Position Value:", utils.formatUnits(positionValue));
console.log('Position Risk:', utils.formatUnits(risk, 2), '%');
console.log("Position Size:", utils.formatUnits(positionInfo.collateralSize));

expect(await bank.isLiquidatable(positionId)).to.be.true;
console.log("Is Liquidatable:", await bank.isLiquidatable(positionId));

console.log("===Portion Liquidated===");
const liqAmount = utils.parseUnits("100", 6);
await usdc.connect(alice).approve(bank.address, liqAmount)
await expect(
bank.connect(alice).liquidate(positionId, USDC, liqAmount)
).to.be.revertedWith("REPAY_ALLOW_NOT_WARMED_UP");
})
it("should be able to liquidate the position when repayAllowed is warmed up => (OV - PV)/CV = LT", async () => {
await evm_increaseTime(4 * 3600);
await evm_mine_blocks(10);
await ichiVault.rebalance(-260400, -260200, -260800, -260600, 0);
let positionInfo = await bank.getPositionInfo(positionId);
Expand Down Expand Up @@ -646,7 +691,7 @@ describe('Bank', () => {
await mockOracle.setPrice(
[ICHI],
[
BigNumber.from(10).pow(17).mul(12), // $1.5
BigNumber.from(10).pow(17).mul(10), // $1
]
);
risk = await bank.getPositionRisk(positionId)
Expand Down

0 comments on commit dfdb119

Please sign in to comment.