Platform: Code4rena
Start Date: 20/01/2022
Pot Size: $50,000 USDC
Total HM: 3
Participants: 35
Period: 7 days
Judge: GalloDaSballo
Total Solo HM: 2
Id: 77
League: ETH
Rank: 9/35
Findings: 2
Award: $527.72
🌟 Selected for report: 4
🚀 Solo Findings: 0
🌟 Selected for report: pauliax
Also found by: 0x1f8b, Jujic, danb, harleythedog
202.8004 USDC - $202.80
pauliax
Conditions should be inclusive >= or <= :
require( baseTokenQty > _baseTokenQtyMin, "MathLib: INSUFFICIENT_BASE_TOKEN_QTY" ); require( quoteTokenQty > _quoteTokenQtyMin, "MathLib: INSUFFICIENT_QUOTE_TOKEN_QTY" ); require( _baseTokenQtyMin < maxBaseTokenQty, "MathLib: INSUFFICIENT_DECAY" ); require( _quoteTokenQtyMin < maxQuoteTokenQty, "MathLib: INSUFFICIENT_DECAY" );
Otherwise, these functions will fail when e.g. baseTokenQty = _baseTokenQtyMin when the end-user expects it to pass through.
#0 - 0xean
2022-01-27T00:21:29Z
yup, makes sense. Will modify.
#1 - GalloDaSballo
2022-02-04T23:55:20Z
Agree with the finding and severity
🌟 Selected for report: harleythedog
202.8004 USDC - $202.80
pauliax
You say: FEE_ON_TRANSFER_NOT_SUPPORTED However, this check is only triggered when the balance of baseToken is 0 (isExchangeEmpty is true). If someone directly sends some dust tokens to the contract, this check will not be triggered.
bool isExchangeEmpty = IERC20(baseToken).balanceOf(address(this)) == 0; // transfer base tokens to Exchange IERC20(baseToken).safeTransferFrom( msg.sender, address(this), tokenQtys.baseTokenQty ); if (isExchangeEmpty) { require( IERC20(baseToken).balanceOf(address(this)) == tokenQtys.baseTokenQty, "Exchange: FEE_ON_TRANSFER_NOT_SUPPORTED" ); }
Also, quoteToken is not checked for the same condition:
// transfer quote tokens to Exchange IERC20(quoteToken).safeTransferFrom( msg.sender, address(this), tokenQtys.quoteTokenQty );
In my opinion, it would be nice to ensure that both tokens were transferred without losses. You can even introduce a helper function that performs these transfers and checks.
I suggest checking balances before/after:
uint256 balanceBefore = IERC20(baseToken).balanceOf(address(this)); // transfer base tokens to Exchange IERC20(baseToken).safeTransferFrom( msg.sender, address(this), tokenQtys.baseTokenQty ); uint256 balanceAfter = IERC20(baseToken).balanceOf(address(this)); require(balanceAfter == balanceBefore + tokenQtys.baseTokenQty, "Exchange: FEE_ON_TRANSFER_NOT_SUPPORTED"); // could be >=
#0 - 0xean
2022-01-27T00:24:45Z
We only need to do the check on the first transfer when the exchange is empty because the tokens never change for a given exchange. For your "attack" vector to be true, someone would have to 1) ignore the documentation 2) create an exchange 3) send dust to the contract outside of the normal workflow 4) and then add liquidity to avoid our check.
We explicitly do not support FOT tokens, and having a check at all is beyond what most protocols do. Additionally, the base token is the token that is supported to have elastic supply and this is why we explicitly check only the base token, to avoid any confusion.
I disagree with the severity entirely since
Fee on Transfer Tokens are NOT supported in our current implementation
is stated in the readme of the contest.
#1 - GalloDaSballo
2022-02-05T23:50:46Z
Duplicate of #119
5.0956 USDC - $5.10
pauliax
Using the unchecked keyword to avoid redundant arithmetic checks and save gas when an underflow/overflow cannot happen, e.g.:
if (rootK > rootKLast) { uint256 numerator = _totalSupplyOfLiquidityTokens * (rootK - rootKLast);
rootK - rootKLast will never underflow here.
#0 - GalloDaSballo
2022-02-04T00:34:31Z
Agree with the finding, typically unchecked
arithmetic will save between 20 and 30 gas
10.4848 USDC - $10.48
pauliax
Result of this.totalSupply() could be cached to avoid duplicate calls:
require(this.totalSupply() > 0, "Exchange: INSUFFICIENT_LIQUIDITY"); ... uint256 totalSupplyOfLiquidityTokens = this.totalSupply();
#0 - GalloDaSballo
2022-02-04T00:48:58Z
Caching totalSupply to memory will yield gas savings. Reading from Storage costs 100 (after first hot read (2.1k)) Reading from memory costs only 3
Agree with finding
🌟 Selected for report: pauliax
106.5364 USDC - $106.54
pauliax
Would be cheapier to have >= here when quoteTokenQtyToReturn = internalBalances.quoteTokenReserveQty to skip math operation:
// We should ensure no possible overflow here. if (quoteTokenQtyToReturn > internalBalances.quoteTokenReserveQty) { internalBalances.quoteTokenReserveQty = 0; } else { internalBalances.quoteTokenReserveQty -= quoteTokenQtyToReturn; }
#0 - GalloDaSballo
2022-02-04T22:47:08Z
The >=
is a check for equality (EQ
) as well as a GT
But the math operation is a SSLOAD then the operation and then the assignment
I agree with the finding