Tiny Gingerbread Tarantula
High
The matchOffersV3 function in the DebitaV3Aggregator.sol contract initializes the amountCollateralPerPrinciple array but reads from it before assigning any values. This oversight leads to skewed calculations, particularly affecting updatedLastWeightAverage and updatedLastApr, same applies to amountPerPrinciple been read from here, and impacting core protocol logic.
The root cause is that the amountCollateralPerPrinciple array is initialized but not populated before being accessed. Specifically, m_amountCollateralPerPrinciple is set to zero by default since it reads from an uninitialized array. This zero value is then used in subsequent calculations, leading to incorrect results, while the documentation did not clearly defined the scope on how the updatedLastWeightAverage
should be calculated as it uses the weightedAverageRatio[principleIndex]
which was not initialized before the call, if amountCollateralPerPrinciple
is properly initialized it will caused an error:
require(
weightedAverageRatio[i] >=
((ratiosForBorrower[i] * 9800) / 10000) &&
weightedAverageRatio[i] <=
(ratiosForBorrower[i] * 10200) / 10000,
"Invalid ratio"
);
cause the weightedAverageRatio[i]
will only be initialized for newWeightedAverage
as updatedLastWeightAverage
will remain 0.
weightedAverageRatio[principleIndex] =
newWeightedAverage +
updatedLastWeightAverage;
Because the updatedLastWeightAverage
will always return 0, weightedAverageRatio
will equals newWeightedAverage + 0
making
uint updatedLastWeightAverage = (weightedAverageRatio[
principleIndex
] * m_amountCollateralPerPrinciple) /
(m_amountCollateralPerPrinciple + userUsedCollateral);
on no-effect
Due to this issue, calculations for updatedLastWeightAverage and updatedLastApr become inaccurate. This will result in incorrect ratios and APRs within the loan matching process, potentially leading to unfair or invalid loan agreements. Specifically, the updatedLastWeightAverage will default to zero if m_amountCollateralPerPrinciple remains unassigned, which skews weighted average calculations and disrupts key protocol logic, and weightedAverageAPR
will always results in newWeightedAPR
which will also affect the requirement statement here
Mock PriceFeed
pragma solidity ^0.8.0;
contract MockPriceFeed {
int256 private price;
uint8 private decimals_;
uint256 private roundId;
uint256 private updatedAt;
constructor(int256 _initialPrice, uint8 _decimals) {
price = _initialPrice;
decimals_ = _decimals;
roundId = 1;
updatedAt = block.timestamp;
}
function setPrice(int256 _price) external {
price = _price;
roundId++;
updatedAt = block.timestamp;
}
function getRoundData(uint80 _roundId)
external
view
returns (
uint80,
int256,
uint256,
uint256,
uint80
)
{
return (_roundId, price, updatedAt, updatedAt, _roundId);
}
function latestRoundData()
external
view
returns (
uint80,
int256,
uint256,
uint256,
uint80
)
{
return (uint80(roundId), price, updatedAt, updatedAt, uint80(roundId));
}
}
contract MockERC20Token is ERC20Mock {
constructor() ERC20Mock() {}
function mint_to(address account, uint256 amount) external {
_mint(account, amount);
}
function decimals() public view virtual override returns (uint8) {
return 6;
}
}
contract DebitaAggregatorTest is Test, DynamicData {
DBOFactory public DBOFactoryContract;
DLOFactory public DLOFactoryContract;
Ownerships public ownershipsContract;
DebitaIncentives public incentivesContract;
DebitaV3Aggregator public DebitaV3AggregatorContract;
auctionFactoryDebita public auctionFactoryDebitaContract;
DynamicData public allDynamicData;
MockPriceFeed public AEROPriceFeed;
MockPriceFeed public USDPriceFeed;
DebitaChainlink public chainlink;
DLOImplementation public LendOrder;
DBOImplementation public BorrowOrder;
ERC20Mock public AEROContract;
address AERO;
MockERC20Token USD;
// address owner
address owner = address(0x011111);
address alice = address(0x0222222);
address bob = address(0x0333333);
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();
AEROPriceFeed = new MockPriceFeed(120000000, 8); // Price of 1 AERO in USD is 1.2
USDPriceFeed = new MockPriceFeed(100000000, 8); // Price of 1 USD in USD is 1
chainlink = new DebitaChainlink(address(0x0), address(this));
AEROContract = new ERC20Mock();
USD = new MockERC20Token();
AERO = address(AEROContract);
chainlink.setPriceFeeds(AERO, address(AEROPriceFeed));
chainlink.setPriceFeeds(address(USD), address(USDPriceFeed));
DebitaV3Loan loanInstance = new DebitaV3Loan();
DebitaV3AggregatorContract = new DebitaV3Aggregator(
address(DLOFactoryContract),
address(DBOFactoryContract),
address(incentivesContract),
address(ownershipsContract),
address(auctionFactoryDebitaContract),
address(loanInstance)
);
DebitaV3AggregatorContract.setOracleEnabled(address(chainlink), true);
ownershipsContract.setDebitaContract(
address(DebitaV3AggregatorContract)
);
auctionFactoryDebitaContract.setAggregator(
address(DebitaV3AggregatorContract)
);
DLOFactoryContract.setAggregatorContract(
address(DebitaV3AggregatorContract)
);
DBOFactoryContract.setAggregatorContract(
address(DebitaV3AggregatorContract)
);
incentivesContract.setAggregatorContract(
address(DebitaV3AggregatorContract)
);
setupOrders();
}
function testUserUsedCollateralCalculation() public {
// Set up test data
address[] memory lendOrders = new address[](1);
uint[] memory lendAmountPerOrder = new uint[](1);
uint[] memory porcentageOfRatioPerLendOrder = new uint[](1);
address[] memory principles = new address[](1);
uint[] memory indexForPrinciple_BorrowOrder = new uint[](1);
uint[] memory indexForCollateral_LendOrder = new uint[](1);
uint[] memory indexPrinciple_LendOrder = new uint[](1);
lendOrders[0] = address(LendOrder);
lendAmountPerOrder[0] = 100 * 10 ** 6;
porcentageOfRatioPerLendOrder[0] = 10000;
principles[0] = address(USD);
indexForPrinciple_BorrowOrder[0] = 0;
indexForCollateral_LendOrder[0] = 0;
indexPrinciple_LendOrder[0] = 0;
uint principleDecimals = IERC20Metadata(principles[0]).decimals();
uint collateralDecimals = IERC20Metadata(address(AERO)).decimals();
uint expectedUserUsedCollateral = (lendAmountPerOrder[0] * (10 ** collateralDecimals)) / (10 ** principleDecimals);
address loan = DebitaV3AggregatorContract.matchOffersV3(
lendOrders, lendAmountPerOrder, porcentageOfRatioPerLendOrder,
address(BorrowOrder), principles, indexForPrinciple_BorrowOrder,
indexForCollateral_LendOrder, indexPrinciple_LendOrder
);
DebitaV3Loan loanContract = DebitaV3Loan(loan);
DebitaV3Loan.infoOfOffers[] memory offers = loanContract.getLoanData()._acceptedOffers;
uint actualUserUsedCollateral = offers[0].collateralUsed;
}
function setupOrders() internal {
USD.mint_to(alice, 1000e18);
USD.mint_to(bob, 1000e18);
deal(address(AEROContract), alice, 1000e18, true);
deal(address(AEROContract), bob, 1000e18, true);
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 oraclesPrinciples = allDynamicData
.getDynamicAddressArray(1);
ratio[0] = 1e18;
oraclesPrinciples[0] = address(chainlink);
acceptedPrinciples[0] = address(USD);
oraclesActivated[0] = true;
ltvs[0] = 10000;
vm.startPrank(bob);
IERC20(AERO).approve(address(DBOFactoryContract), 1000e18);
address borrowOrderAddress = DBOFactoryContract.createBorrowOrder(
oraclesActivated,
ltvs,
10000,
864000,
acceptedPrinciples,
AERO,
false,
0,
oraclesPrinciples,
ratio,
address(chainlink),
500e18
);
vm.startPrank(alice);
IERC20(USD).approve(address(DLOFactoryContract), 1000e18);
address[] memory acceptedLendCollateral = allDynamicData
.getDynamicAddressArray(1);
acceptedLendCollateral[0] = address(AERO);
address lendOrderAddress = DLOFactoryContract.createLendOrder(
false,
oraclesActivated,
false,
ltvs,
10000,
8640000,
86400,
acceptedLendCollateral,
address(USD),
oraclesPrinciples,
ratio,
address(chainlink),
500e18
);
LendOrder = DLOImplementation(lendOrderAddress);
BorrowOrder = DBOImplementation(borrowOrderAddress);
}
}
Ensure there is proper initializations of values that affects the logics to calculate