Platform: Code4rena
Start Date: 10/03/2022
Pot Size: $75,000 USDT
Total HM: 25
Participants: 54
Period: 7 days
Judge: pauliax
Total Solo HM: 10
Id: 97
League: ETH
Rank: 2/54
Findings: 10
Award: $8,636.76
🌟 Selected for report: 5
🚀 Solo Findings: 0
🌟 Selected for report: cmichel
3112.8246 USDT - $3,112.82
The depositErc20
function allows setting tokenAddress = NATIVE
and does not throw an error.
No matter the amount
chosen, the SafeERC20Upgradeable.safeTransferFrom(IERC20Upgradeable(tokenAddress), sender, address(this), amount);
call will not revert because it performs a low-level call to NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
, which is an EOA, and the low-level calls to EOAs always succeed.
Because the safe*
version is used, the EOA not returning any data does not revert either.
This allows an attacker to deposit infinite native tokens by not paying anything.
The contract will emit the same Deposit
event as a real depositNative
call and the attacker receives the native funds on the other chain.
Check tokenAddress != NATIVE
in depositErc20
.
#0 - pauliax
2022-04-11T12:12:06Z
Great find, definitely deserves a severity of high.
🌟 Selected for report: minhquanym
When updating the incentivePool
it divides the previous value by BASE_DIVISOR
.
On each update, the incentivePool
basically resets itself to only the increment and loses the previous incentive pool.
// @audit divides entire previous incentivePool by BASE_DIVISOR? wrong parenthesis incentivePool[tokenAddress] = (incentivePool[tokenAddress] + // @audit-info wants to add excess fee (fee - eq) * amount to incentive, eq*amount is lpFee (amount * (transferFeePerc - tokenManager.getTokensInfo(tokenAddress).equilibriumFee))) / BASE_DIVISOR;
The incentive pool never really grows, breaking the economics of the entire protocol that relies on incentivizing low-liquidity pools with this pool.
Fix the computation:
incentivePool[tokenAddress] += (amount * (transferFeePerc - tokenManager.getTokensInfo(tokenAddress).equilibriumFee)) / BASE_DIVISOR;
#0 - ankurdubey521
2022-03-30T09:46:28Z
Duplicate of #38
#1 - pauliax
2022-04-11T12:33:20Z
A duplicate of #38
🌟 Selected for report: cmichel
Also found by: PPrieditis, kyliek, pedroais
378.2082 USDT - $378.21
Supported tokens can be turned off again by calling TokenManager.removeSupportedToken
.
Users won't be able to withdraw their liquidity anymore because of this check in removeLiquidity
.
Consider allowing withdrawals even if the token was unsupported to allow users to reclaim their funds.
#0 - pauliax
2022-04-11T13:05:46Z
A valid concern, assets not at direct risk.
99.257 USDT - $99.26
The executors can set their own tokenGasPrice
arbitrarily high when calling sendFundsToUser
, receiving huge gas rewards which are subtracted from the user's amount to receive.
The user can lose all funds because of this.
While the executors need to be fully trusted anyway, I still think this is an unnecessary additional trust assumption on the executors.
The trust should be removed as much as possible.
The owner should set a global tokenGasPrice
that is used for gas refunds instead of executors choosing their own gas price.
#0 - ankurdubey521
2022-03-30T11:41:47Z
While I agree there is a trust assumption with the executors, the problem with setting a global tokenGasPrice would be that the prices of tokens keep changing, and since we already trust the executors in the current model I think it's acceptable to let them set the tokenGasPrice themselves.
#1 - pauliax
2022-04-30T17:00:13Z
🌟 Selected for report: cmichel
933.8474 USDT - $933.85
Owners can change the liquidityPool
variable any time with the setLiquidityPool
function.
If a liquidity pool was already set and users added liquidity with addTokenLiquidity
, the tokens are directly transferred to the liquidity pool and not kept in the LiquidityProviders
contract.
Changing the liquidityPool
to a different contract will make it impossible for the users to withdraw their liquidity using removeLiquidity
because the tokens are still in the old liquidityPool
and cannot be retrieved.
All users will lose their funds.
Changing the liquidityPool
requires a sophisticated migration mechanism.
Only allow setting the liquidityPool
contract once.
#0 - pauliax
2022-05-02T07:09:42Z
A valid concern, but I am downgrading this to Medium risk because the funds are not lost forever, the same old liquidityPool can be set again by the owner in such a case.
🌟 Selected for report: cmichel
933.8474 USDT - $933.85
Owners can change the lpToken
variable at any time with the setLpToken
function.
If an LP token was already set and users added liquidity with addTokenLiquidity
and were minted a lpToken
NFT, changing the lpToken
to a different contract will make it impossible for the users to withdraw their liquidity using removeLiquidity
.
All users will lose their funds.
Changing the lpToken
requires a sophisticated migration mechanism.
Only allow setting the lpToken
contract once.
#0 - pauliax
2022-05-02T07:09:51Z
A valid concern, but I am downgrading this to Medium risk because the funds are not lost forever, the same old lpToken can be set again by the owner in such a case.
560.3084 USDT - $560.31
The gasPrice
in getAmountToTransfer
is in native tokens and it is subtracted from the user's requested token
which is not the native token, is valued differently, and might even have different decimals.
// @audit in native tokens uint256 totalGasUsed = initialGas - gasleft(); uint256 gasFee = totalGasUsed * tokenGasPrice; // @audit amount and transferFeeAmount are in `tokenAddress`, cannot just subtract gasFee amountToTransfer = amount - (transferFeeAmount + gasFee);
Receiving non-native tokens on a chain will almost always lead to reverts or losses for users because of this wrong amountToTransfer
computation.
sendFundsToUser(tokenAddress=USDC, amount=1e9, tokenGasPrice>0)
and then amountToTransfer = 1e9 - gasFee
will most likely underflow or take a huge cut on the user's USDC.The gasPrice
cannot be a haircut on the amount
as it is not always the native token.
#0 - ankurdubey521
2022-03-30T09:51:52Z
uint256 gasFee = totalGasUsed * tokenGasPrice;
The tokenGasPrice here is the current gas price expressed in terms of the token being transferred, and is something which is passed to the contract in call by the backend. Therefore the gasFee comes out to be in terms of the token itself
#1 - pauliax
2022-04-30T17:19:06Z
🌟 Selected for report: cmichel
Also found by: CertoraInc, cccz
The public sharesToTokenAmount
function does not check if the denominator totalSharesMinted[_tokenAddress]
is zero.
Neither do the callers of this function. The function will revert.
Calling functions like getFeeAccumulatedOnNft
and sharesToTokenAmount
from another contract should never revert.
Return 0 in case totalSharesMinted[_tokenAddress]
is zero.
#0 - ankurdubey521
2022-03-30T11:37:51Z
Duplicate of #48
#1 - pauliax
2022-05-09T11:00:35Z
A valid concern of runtime error, I am marking this as a primary issue.
🌟 Selected for report: hickuphh3
Also found by: 0v3rf10w, 0x1f8b, 0xDjango, 0xNazgul, 0xngndev, 0xwags, Cantor_Dust, CertoraInc, Dravee, IllIllI, PPrieditis, Ruhum, TerrierLover, WatchPug, XDms, benk10, berndartmueller, bitbopper, catchup, cmichel, cryptphi, csanuragjain, danb, defsec, gzeon, hagrid, hubble, jayjonah8, kenta, kyliek, minhquanym, rfa, robee, saian, samruna, throttle, ye0lde, z3s
698.2015 USDT - $698.20
initialize
functionsaddExecutors
does not need onlyOwner
modifier because addExecutor
already has itremoveExecutors
does not need onlyOwner
modifier because removeExecutor
already has itonlyValidLpToken
function does not use the token
return variable of the tokenMetadata
call. The call can be removedLiquidityPool.depositErc20
. The amount
value is stored and available to be withdrawn later but the contract receives amount - fees
. Note that there are tokenChecks(tokenAddress)
and the protocol would need to whitelist these non-standard tokens first.receive()
function. Check if it's really needed or consider restricting the callers to contracts that call it.* 1e11
/ 1e11
is not useful and does not give more precision. It can be removedsendFundsToUser
to transfer out any funds as they please. Consider adding threshold signatures to prevent all funds from being lost when a single executor is compromised.require(IERC20Upgradeable(tokenAddress).balanceOf(address(this)) >= amountToTransfer, "Not Enough Balance")
. So in theory a transfer could steal the fees of LPs but it reverts earlier in getAmountToTransfer
-> getTransferFee
if that would be the case. The check should still use getCurrentLiquidity(tokenAddress)
instead of the contract's balance.ifEnabled
. Equivalent to shorter !areWhiteListRestrictionsEnabled || _cond
_getDigitsCount(fractionalPartTemp)
in a variable instead of doing 2 calls.class="..."
attributes. The point of storing the SVGs on-chain is that they are fully self-contained and don't really on any off-chain parts to be able to reconstruct them. The CSS class is however not defined in the SVG and thus the image is determined by however the off-chain class is defined, defeating the purpose.#0 - ankurdubey521
2022-03-30T17:41:18Z
Duplicate of #137
#1 - pauliax
2022-05-12T16:02:10Z
"Protocol does not support fee-on-transfer tokens" should be upgraded to Medium: https://github.com/code-423n4/2022-03-biconomy-findings/issues/91#issuecomment-1109645407
#2 - pauliax
2022-05-12T16:02:58Z
"Users can lose native tokens because contracts have a receive() function" should be upgraded to Medium: #157
#3 - pauliax
2022-05-12T16:04:16Z
"Executors need to be trusted" should be upgraded to Medium: #161