Platform: Code4rena
Start Date: 07/04/2023
Pot Size: $47,000 USDC
Total HM: 20
Participants: 120
Period: 6 days
Judge: GalloDaSballo
Total Solo HM: 4
Id: 230
League: ETH
Rank: 117/120
Findings: 1
Award: $5.98
🌟 Selected for report: 0
🚀 Solo Findings: 0
5.9827 USDC - $5.98
https://github.com/code-423n4/2023-04-caviar/blob/cd8a92667bcb6657f70657183769c244d04c015c/src/PrivatePool.sol#L385-L417 https://github.com/code-423n4/2023-04-caviar/blob/cd8a92667bcb6657f70657183769c244d04c015c/src/PrivatePool.sol#L731-L738
When the PrivatePool::change()
is called, fees are calculated using the changeFeeQuote
function.
function change( uint256[] memory inputTokenIds, uint256[] memory inputTokenWeights, MerkleMultiProof memory inputProof, IStolenNftOracle.Message[] memory stolenNftProofs, uint256[] memory outputTokenIds, uint256[] memory outputTokenWeights, MerkleMultiProof memory outputProof ) public payable returns (uint256 feeAmount, uint256 protocolFeeAmount) { // ~~~ Checks ~~~ // // check that the caller sent 0 ETH if base token is not ETH if (baseToken != address(0) && msg.value > 0) revert InvalidEthAmount(); // check that NFTs are not stolen if (useStolenNftOracle) { IStolenNftOracle(stolenNftOracle).validateTokensAreNotStolen(nft, inputTokenIds, stolenNftProofs); } // fix stack too deep { // calculate the sum of weights for the input nfts uint256 inputWeightSum = sumWeightsAndValidateProof(inputTokenIds, inputTokenWeights, inputProof); // calculate the sum of weights for the output nfts uint256 outputWeightSum = sumWeightsAndValidateProof(outputTokenIds, outputTokenWeights, outputProof); // check that the input weights are greater than or equal to the output weights if (inputWeightSum < outputWeightSum) revert InsufficientInputWeight(); // calculate the fee amount (feeAmount, protocolFeeAmount) = changeFeeQuote(inputWeightSum); }
function changeFeeQuote(uint256 inputAmount) public view returns (uint256 feeAmount, uint256 protocolFeeAmount) { // multiply the changeFee to get the fee per NFT (4 decimals of accuracy) uint256 exponent = baseToken == address(0) ? 18 - 4 : ERC20(baseToken).decimals() - 4; uint256 feePerNft = changeFee * 10 ** exponent; feeAmount = inputAmount * feePerNft / 1e18; protocolFeeAmount = feeAmount * Factory(factory).protocolFeeRate() / 10_000; }
The issue is that when calcuating the exponent
in the changeFeeQuote
function, the subtraction ERC20(baseToken).decimals() - 4;
will result in an underflow for tokens with less than 4 decimals.
Note that some popular tokens such as EURS
with 2 decimals would fail.
This means that the protocol would break when a user attempts to perform a change with tokens with less than 4 decimals.
Modify the test/shared/ShibaInu.sol
file to set the token to 3 decimals:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "solmate/tokens/ERC20.sol"; contract ShibaInu is ERC20 { constructor() ERC20("Shiba Inu", "SHIB", 3) {} }
Then run the test:
forge test -vvv --ffi --match-path test/PrivatePool/Change.t.sol
This will revert with the following error:
├─ [1393] PrivatePool::changeFeeQuote(3000000000000000000) [staticcall] │ ├─ [293] ShibaInu::decimals() [staticcall] │ │ └─ ← 3 │ └─ ← "Arithmetic over/underflow" └─ ← "Arithmetic over/underflow"
It may be advisable to use a scaling factor in the protocol when dealing with tokens to ensure the underflow does not happen
#0 - c4-pre-sort
2023-04-19T07:53:58Z
0xSorryNotSorry marked the issue as high quality report
#1 - c4-pre-sort
2023-04-20T15:22:47Z
0xSorryNotSorry marked the issue as duplicate of #858
#2 - c4-judge
2023-05-01T07:14:46Z
GalloDaSballo marked the issue as satisfactory