claimConcentratedRewards and claimAmbientRewards call external contracts via call. This could leave the contract vulnerable to reentrancy attacks if the recipient contract calls back into the contract before updating state.
Proof of Concept
The calls to external contracts occur on these lines:
Line 281:
(bool sent, ) = owner.call{value: rewardsToSend}("");
The issue is that .call is used to send Ether to the owner's address. This makes the contract vulnerable to reentrancy.
For example, if the owner's contract has a fallback function that calls back into claimRewards again before the state is updated, it could continually drain funds.
Here is an in-depth explanation of the reentrancy vulnerability introduced by using .call in claimConcentratedRewards and claimAmbientRewards:
You can see that these functions send Ether to user addresses after calculating rewards:
The problem is call forwards all available gas to the callee and returns any data or error values from the call. This allows the user's contract to execute code and call back into the original contract while claimConcentratedRewards is still executing.
For example, the user could have a fallback function that calls claimConcentratedRewards again:
This separates the external call from the state changes, preventing reentrancy.
The key is to avoid calling external contracts via call when in the middle of updating state. Transferring funds separately after state changes is a safer pattern.
Lines of code
https://github.com/code-423n4/2023-10-canto/blob/40edbe0c9558b478c84336aaad9b9626e5d99f34/canto_ambient/contracts/mixins/LiquidityMining.sol#L156-L196 https://github.com/code-423n4/2023-10-canto/blob/40edbe0c9558b478c84336aaad9b9626e5d99f34/canto_ambient/contracts/mixins/LiquidityMining.sol#L256-L290
Vulnerability details
Impact
claimConcentratedRewards
andclaimAmbientRewards
call external contracts viacall
. This could leave the contract vulnerable to reentrancy attacks if the recipient contract calls back into the contract before updating state.Proof of Concept
The calls to external contracts occur on these lines:
claimConcentratedRewards
claimAmbientRewards
The issue is that
.call
is used to send Ether to the owner's address. This makes the contract vulnerable to reentrancy.For example, if the owner's contract has a fallback function that calls back into
claimRewards
again before the state is updated, it could continually drain funds.You can see that these functions send Ether to user addresses after calculating rewards:
The problem is call forwards all available gas to the callee and returns any data or error values from the call. This allows the user's contract to execute code and call back into the original contract while claimConcentratedRewards is still executing.
For example, the user could have a fallback function that calls claimConcentratedRewards again:
So, here is the full execution flow would be:
LiquidityMining
callsclaimConcentratedRewards
LiquidityMining.claimConcentratedRewards
againThe rewards state was never updated in
LiquidityMining
, so Attack can continuously drain funds.Tools Used
Vs
Recommended Mitigation Steps
Using call{value: amount}("")
prevents reentrancy but is still unsafe as it provides no gas stipend and could fail if the recipient is a contract.A better solution is to use the transfer function which is safe against reentrancy:
Or send Ether in a separate transaction after updating state:
This separates the external call from the state changes, preventing reentrancy.
The key is to avoid calling external contracts via call when in the middle of updating state. Transferring funds separately after state changes is a safer pattern.
Assessed type
Reentrancy