After combining more commits with this mitigation, the following protocolChangeFeeRate and Factory.setProtocolChangeFeeRate functions are added in the Factory contract. Because of if (_protocolChangeFeeRate > 10_000) revert ProtocolChangeFeeRateTooHigh(), protocolChangeFeeRate can be set to up to 10_000. Based on the sponsor's comment, protocolChangeFeeRate could be set to a reasonable value, such as 3000 that is for 30%.
/// @notice The protocol change fee rate that is taken on each change/flash loan. It's in basis points: 200 = 2.00%.
uint16 public protocolChangeFeeRate;
function setProtocolChangeFeeRate(uint16 _protocolChangeFeeRate) public onlyOwner {
// check that the protocol change fee rate is not higher than 100%
if (_protocolChangeFeeRate > 10_000) revert ProtocolChangeFeeRateTooHigh();
protocolChangeFeeRate = _protocolChangeFeeRate;
emit SetProtocolChangeFeeRate(_protocolChangeFeeRate);
}
The following PrivatePool.changeFeeQuote and PrivatePool.flashFeeAndProtocolFee functions are also updated to execute protocolFeeAmount = feeAmount * Factory(factory).protocolChangeFeeRate() / 10_000, where Factory(factory).protocolChangeFeeRate() is used instead of Factory(factory).protocolFeeRate(). As shown by the Factory.setProtocolFeeRate function below, the highest possible protocolFeeRate would be 500 so protocolChangeFeeRate would be much higher than protocolFeeRate. This makes sense because protocolFeeAmount is a fee on top of feeAmount that is for the private pool, and changeFee that feeAmount depends on can be somewhat small; changing protocolFeeAmount to depend on protocolChangeFeeRate instead of protocolFeeRate can make protocolFeeAmount not as small as before. Thus, the corresponding issue is mitigated.
function setProtocolFeeRate(uint16 _protocolFeeRate) public onlyOwner {
// check that the protocol fee rate is not higher than 5%
if (_protocolFeeRate > 500) revert ProtocolFeeRateTooHigh();
protocolFeeRate = _protocolFeeRate;
emit SetProtocolFeeRate(_protocolFeeRate);
}
Yet, it is worth to note that the PrivatePool.changeFeeQuote and PrivatePool.flashFeeAndProtocolFee functions are still not consistent with the following PrivatePool.buyQuote and PrivatePool.sellQuote functions. In the PrivatePool.buyQuote and PrivatePool.sellQuote functions, protocolFeeAmount is not a fee on top of feeAmount so feeRate set by the private pool owner does not affect protocolFeeAmount. In contrast, in the PrivatePool.changeFeeQuote and PrivatePool.flashFeeAndProtocolFee functions, changeFee set by the private pool owner can affect protocolFeeAmount in which protocolFeeAmount could be 0 if the private pool owner sets changeFee to 0. Although this appears to be a design choice, the sponsor is encouraged to further review the differences among the PrivatePool.changeFeeQuote, PrivatePool.flashFeeAndProtocolFee, PrivatePool.buyQuote and PrivatePool.sellQuote functions to determine whether these functions need to be consistent or not.
Mitigation of M-10: Issue mitigated (Please see comments)
Issue
M-10: Incorrect protocol fee is taken when changing NFTs
Mitigation
https://github.com/outdoteth/caviar-private-pools/pull/13
Assessment of Mitigation
Issue mitigated (Please see comments)
Comments
After combining more commits with this mitigation, the following
protocolChangeFeeRate
andFactory.setProtocolChangeFeeRate
functions are added in theFactory
contract. Because ofif (_protocolChangeFeeRate > 10_000) revert ProtocolChangeFeeRateTooHigh()
,protocolChangeFeeRate
can be set to up to10_000
. Based on the sponsor's comment,protocolChangeFeeRate
could be set to a reasonable value, such as 3000 that is for 30%.https://github.com/outdoteth/caviar-private-pools/blob/main/src/Factory.sol#L61-L62
https://github.com/outdoteth/caviar-private-pools/blob/main/src/Factory.sol#L165-L171
The following
PrivatePool.changeFeeQuote
andPrivatePool.flashFeeAndProtocolFee
functions are also updated to executeprotocolFeeAmount = feeAmount * Factory(factory).protocolChangeFeeRate() / 10_000
, whereFactory(factory).protocolChangeFeeRate()
is used instead ofFactory(factory).protocolFeeRate()
. As shown by theFactory.setProtocolFeeRate
function below, the highest possibleprotocolFeeRate
would be 500 soprotocolChangeFeeRate
would be much higher thanprotocolFeeRate
. This makes sense becauseprotocolFeeAmount
is a fee on top offeeAmount
that is for the private pool, andchangeFee
thatfeeAmount
depends on can be somewhat small; changingprotocolFeeAmount
to depend onprotocolChangeFeeRate
instead ofprotocolFeeRate
can makeprotocolFeeAmount
not as small as before. Thus, the corresponding issue is mitigated.https://github.com/outdoteth/caviar-private-pools/blob/main/src/PrivatePool.sol#L780-L787
https://github.com/outdoteth/caviar-private-pools/blob/main/src/PrivatePool.sol#L800-L805
https://github.com/outdoteth/caviar-private-pools/blob/main/src/Factory.sol#L155-L161
Yet, it is worth to note that the
PrivatePool.changeFeeQuote
andPrivatePool.flashFeeAndProtocolFee
functions are still not consistent with the followingPrivatePool.buyQuote
andPrivatePool.sellQuote
functions. In thePrivatePool.buyQuote
andPrivatePool.sellQuote
functions,protocolFeeAmount
is not a fee on top offeeAmount
sofeeRate
set by the private pool owner does not affectprotocolFeeAmount
. In contrast, in thePrivatePool.changeFeeQuote
andPrivatePool.flashFeeAndProtocolFee
functions,changeFee
set by the private pool owner can affectprotocolFeeAmount
in whichprotocolFeeAmount
could be 0 if the private pool owner setschangeFee
to 0. Although this appears to be a design choice, the sponsor is encouraged to further review the differences among thePrivatePool.changeFeeQuote
,PrivatePool.flashFeeAndProtocolFee
,PrivatePool.buyQuote
andPrivatePool.sellQuote
functions to determine whether these functions need to be consistent or not.https://github.com/outdoteth/caviar-private-pools/blob/main/src/PrivatePool.sol#L742-L754
https://github.com/outdoteth/caviar-private-pools/blob/main/src/PrivatePool.sol#L762-L773