When the protocol compute the value of fees, it overestimete it and then make the protocol insolvent.
function _handleWethRewardDistribution(
Market.Data storage market,
address assetOut,
UD60x18 receivedWethX18
)
internal
{
UD60x18 feeRecipientsSharesX18 = ud60x18(MarketMakingEngineConfiguration.load().totalFeeRecipientsShares);
UD60x18 receivedProtocolWethRewardX18 = receivedWethX18.mul(feeRecipientsSharesX18);
UD60x18 receivedVaultsWethRewardX18 =
receivedWethX18.mul(ud60x18(Constants.MAX_SHARES).sub(feeRecipientsSharesX18));
UD60x18 leftover = receivedWethX18.sub(receivedProtocolWethRewardX18).sub(receivedVaultsWethRewardX18);
receivedVaultsWethRewardX18 = receivedVaultsWethRewardX18.add(leftover);
market.receiveWethReward(assetOut, receivedProtocolWethRewardX18, receivedVaultsWethRewardX18);
Vault.recalculateVaultsCreditCapacity(market.getConnectedVaultsIds());
}
The protocol will overestimate the fees because of rounding errors. We can demonstrated with a simple POC.
You should have this output we clearly see after the fee conversion the protocol is insolvent :
uint128[] feeConversionMarketIds;
address[] feeConversionAssets;
function test_insufficientBalance() public {
address bob= 0x0000000000000000000000000000000000010000;
address keeper = users.keepersForwarder.account;
VaultConfig memory fuzzVaultConfig1 = getFuzzVaultConfig(8);
VaultConfig memory fuzzVaultConfig2 = getFuzzVaultConfig(14);
usdc.mint(bob, 100_000e6);
vm.stopPrank();
vm.startPrank(bob);
usdc.approve(address(marketMakingEngine), 100_000e6);
IERC20(fuzzVaultConfig1.indexToken).approve(address(marketMakingEngine), type(uint).max);
IERC20(fuzzVaultConfig2.indexToken).approve(address(marketMakingEngine), type(uint).max);
wEth.mint(bob, 100_000e18);
wEth.approve(address(marketMakingEngine), 100_000e18);
marketMakingEngine.deposit(8, 65046691939 , 27447489388 , "", false);
marketMakingEngine.deposit(14, 1624467398489052174 , 0 , "", false);
PerpMarketCreditConfig memory currentPerpMarketCreditConfig1 = perpMarketsCreditConfig[uint256(3)];
usdc.mint(currentPerpMarketCreditConfig1.engine,434962687);
vm.stopPrank();
vm.startPrank(currentPerpMarketCreditConfig1.engine);
usdc.approve(address(marketMakingEngine), 434962687);
marketMakingEngine.receiveMarketFee(3, address(usdc), 434962687);
vm.stopPrank();
vm.prank(bob);
marketMakingEngine.stake(8, 100000 );
vm.prank(bob);
marketMakingEngine.stake(14, 100146 );
vm.startPrank( users.owner.account);
configureFeeConversionKeeper(1, uint128(1));
FeeConversionKeeper(feeConversionKeeper).setForwarder(keeper);
vm.stopPrank();
(bool upkeepNeeded, bytes memory performData) = FeeConversionKeeper(feeConversionKeeper).checkUpkeep("");
if(upkeepNeeded) {
(uint128[] memory marketIds, address[] memory assets) = abi.decode(performData, (uint128[], address[]));
for(uint i; i<assets.length; i++) {
if(assets[i] !=address(0)) {
feeConversionAssets.push(assets[i]);
feeConversionMarketIds.push(marketIds[i]);
}
}
}
vm.prank(keeper);
FeeConversionKeeper(feeConversionKeeper).performUpkeep(abi.encode(feeConversionMarketIds, feeConversionAssets)) ;
console2.log("This is the earned fees after swap fees %d",marketMakingEngine.workaround_getPendingProtocolWethReward(3)+marketMakingEngine.getEarnedFees(8,0x0000000000000000000000000000000000010000)+marketMakingEngine.getEarnedFees(14,0x0000000000000000000000000000000000010000));
console2.log("This is the balance after swap fees : %d",wEth.balanceOf(address(marketMakingEngine)));
vm.prank(bob);
marketMakingEngine.claimFees(14);
PerpMarketCreditConfig memory currentPerpMarketCreditConfig = perpMarketsCreditConfig[uint256(3)];
vm.prank(currentPerpMarketCreditConfig.engine);
marketMakingEngine.sendWethToFeeRecipients(3);
vm.prank(bob);
marketMakingEngine.claimFees(8);
}
fees will not be claimable.
Echidna.