Platform: Code4rena
Start Date: 27/11/2023
Pot Size: $36,500 USDC
Total HM: 0
Participants: 22
Period: 8 days
Judge: 0xA5DF
Id: 308
League: ETH
Rank: 4/22
Findings: 1
Award: $5,172.50
🌟 Selected for report: 0
🚀 Solo Findings: 0
5172.5015 USDC - $5,172.50
https://github.com/code-423n4/2023-11-shellprotocol/blob/main/src/adapters/OceanAdapter.sol#L70-L74
In the computeOutputAmount
function of OceanAdapter.sol
, the unwrappedAmount
is calculated by subtracting the unwrapFee
from the inputAmount
. This unwrappedAmount
is then passed as a parameter to the primitiveOutputAmount
function. However, the unwrap fee may be higher than the unwrappedAmount
due to truncation, in which case the value passed to the primitiveOutputAmount
function is higher than the actual available amount.
The code in question:
uint256 unwrapFee = inputAmount / IOceanInteractions(ocean).unwrapFeeDivisor(); uint256 unwrappedAmount = inputAmount - unwrapFee; outputAmount = primitiveOutputAmount(inputToken, outputToken, unwrappedAmount, metadata);
The code implicitly assumes that Adapters need to truncate the unwrappedAmount
to the token's decimals.
Instead of making the assumption about the truncation, the unwrapped amount should be inferred from the balance change and passed to primitiveOutputAmount
, ideally already in the token's precision. This would ensure that the correct amount is used in the calculations and prevent potential overcharging.
Curve2PoolAdapter.sol#L100
In the primitiveOutputAmount
function of the Curve2PoolAdapter
contract, the inputAmount
parameter is passed to the Swap
event without being converted to rawInputAmount
. This could potentially lead to incorrect event logs as the inputAmount
might not reflect the actual amount used in the swap operation due to decimal conversions. The rawInputAmount
is the value that is actually used in the swap operation, and it is derived from inputAmount
after adjusting for decimal differences.
Relevant code:
if (action == ComputeType.Swap) { emit Swap(inputToken, inputAmount, outputAmount, minimumOutputAmount, primitive, true); }
To ensure the event logs accurately reflect the operations performed, it is recommended to emit the Swap
event with rawInputAmount
instead of inputAmount
. This will ensure that the logged amount is the actual amount used in the swap operation. The code should be updated as follows:
if (action == ComputeType.Swap) { emit Swap(inputToken, rawInputAmount, outputAmount, minimumOutputAmount, primitive, true); }
src/ocean/BalanceDelta.sol#L100
The comment in the code suggests that the maximum value of int256
is one unit higher than the absolute value of the minimum value. However, this is incorrect. The maximum value of int256
is actually one unit lower than the absolute value of the minimum value. This discrepancy could lead to confusion and potential errors in the future if the code is misunderstood. The relevant code is:
if (uint256(type(int256).max) <= amount) revert CAST_AMOUNT_EXCEEDED();
In addition, given the above, it is also safe to use strict equality in the above comparison, since type(int256).max is safely castable to int256 (and also has a negative representation).
The documentation should be corrected to accurately reflect the properties of int256
. The correct statement should be: "the maximum value of int256
is one unit lower than the absolute value of the minimum value". This will ensure that the documentation is accurate and does not lead to potential misunderstandings or errors in the future.
The contract CurveTricryptoAdapter
includes a fallback function that is not necessary. The fallback function is an unnamed function that is executed whenever the contract is called with function data that does not match any of the existing functions. However, in this case, it is not required and can be replaced with a receive()
function which is triggered when the call data is empty.
Remove the fallback function and replace it with a receive()
function. The receive()
function is a special function that has no arguments, cannot return anything and must have external visibility. It is executed on a call to the contract with empty calldata.
receive() external payable { }
CurveTricryptoAdapter.sol#L100
The fallback function in the CurveTricryptoAdapter
contract does not include a check for the sender's address. This could potentially lead to Ether being locked in the contract if it is sent from an address that is not expected or allowed.
Add a check for the sender's address in the fallback function to ensure that Ether is only accepted from expected or allowed addresses. This can be done using a modifier or a simple require()
statement.
fallback() external payable { require(msg.sender == expectedAddress, "Unexpected sender"); }
Please replace expectedAddress
with the actual address or condition that needs to be checked.
In the Swap
, Deposit
and Withdraw
event declaration, the slippageProtection
parameter is currently of type bytes32
. However, considering its usage and context, it would make more sense for it to be of type uint256
.
Additionally, the name slippageProtection
could be improved to better reflect its purpose. A more appropriate name could be minOutput
, which clearly indicates that it represents the minimum output amount the user expects to receive, providing protection against price slippage.
event Swap( uint256 inputToken, uint256 inputAmount, uint256 outputAmount, bytes32 slippageProtection, address user, bool computeOutput );
Change the type of slippageProtection
to uint256
and rename it to minOutput
.
https://github.com/code-423n4/2023-11-shellprotocol/blob/main/src/ocean/Ocean.sol
In the Ocean contract, there is an inconsistency in how the wrapping of ETH is accounted for as interactions. When ETH is wrapped in a call to doInteraction
or forwardedDoInteraction
, this is counted as an interaction in the numberOfInteractions
parameter of the emitted OceanTransaction
event. However, if ETH is wrapped as part of a call to doMultipleInteractions
or forwardedDoMultipleInteractions
, the emitted OceanTransaction
event does not account for that action as an interaction. This inconsistency could lead to confusion and potential misinterpretation of the events.
It is recommended to check for msg.value
in the doMultipleInteractions
and forwardedDoMultipleInteractions
functions and add 1 to the number of interactions if present. This would ensure that the wrapping of ETH is consistently accounted for as an interaction across all relevant functions.
https://github.com/code-423n4/2023-11-shellprotocol/blob/main/src/ocean/BalanceDelta.sol
In the _getNegativeBalanceDelta
function, there is a potential edge case where the function will revert if amount
equals type(int256).min
. This is due to the line of code return uint256(-amount);
. In the case where amount
equals type(int256).min
, negating amount
will result in a value that cannot be represented as an int256
, causing a revert.
function _getNegativeBalanceDelta(BalanceDelta[] memory self, uint256 tokenId) private pure returns (uint256) { uint256 index = _findIndexOfTokenId(self, tokenId); int256 amount = self[index].delta; if (amount > 0) revert DELTA_AMOUNT_IS_POSITIVE(); return uint256(-amount); }
While this is an edge case, it could potentially disrupt the execution of transactions that hit this condition.
To handle this edge case, consider adding a specific condition to check if amount
equals type(int256).min
before the negation operation. If it does, handle it appropriately based on the business logic of your contract. This could involve reverting with a more descriptive error message, or assigning a maximum possible value to the negated amount
.
https://github.com/code-423n4/2023-11-shellprotocol/blob/main/src/adapters/Curve2PoolAdapter.sol
In the Curve2PoolAdapter
and CurveTricryptoAdapter
contracts, the function _determineComputeType
not only determines the type of computation (Swap, Deposit, Withdraw) based on the input and output tokens, but also validates the tokens. The function name _determineComputeType
does not accurately reflect this validation behavior. Misleading function names can lead to confusion for developers and auditors, potentially leading to overlooked issues or bugs.
Relevant code:
if (((inputToken == xToken) && (outputToken == yToken)) || ((inputToken == yToken) && (outputToken == xToken)))
To improve code readability and maintainability, consider renaming the function to _determineComputeTypeAndValidateTokens
. This name more accurately describes the function's behavior, which includes both determining the computation type and validating the tokens.
https://github.com/code-423n4/2023-11-shellprotocol/blob/main/src/adapters/Curve2PoolAdapter.sol
In the primitiveOutputAmount
function, the variable indexOfInputAmount
is used to store the index of the input token in the Curve pool. The current name of the variable suggests that it is storing the index of an amount, which is misleading. This could lead to confusion for developers reading or maintaining the code. The relevant code is:
int128 indexOfInputAmount = indexOf[inputToken];
To improve code readability and maintainability, it is recommended to rename the variable indexOfInputAmount
to indexOfInputToken
. This name accurately reflects the data that the variable is storing.
https://github.com/code-423n4/2023-11-shellprotocol/blob/main/src/adapters/Curve2PoolAdapter.sol
In the Curve2PoolAdapter
contract, there is an inconsistency in the naming of variables representing Ocean IDs. The variables xToken
and yToken
are named without the 'Id' suffix, while lpTokenId
includes the 'Id' suffix. This inconsistency can lead to confusion and potential errors when reading or modifying the code.
Relevant code:
/// @notice x token Ocean ID. uint256 public immutable xToken; /// @notice y token Ocean ID. uint256 public immutable yToken; /// @notice lp token Ocean ID. uint256 public immutable lpTokenId;
To improve code readability and maintainability, it is recommended to follow a consistent naming convention for similar variables. In this case, either add 'Id' to xToken
and yToken
to become xTokenId
and yTokenId
, or remove 'Id' from lpTokenId
to become lpToken
.
#0 - raymondfam
2023-12-10T20:25:49Z
Possible upgrade:
L-01 --> #252
#1 - c4-pre-sort
2023-12-10T20:27:18Z
raymondfam marked the issue as sufficient quality report
#2 - c4-sponsor
2023-12-13T05:21:00Z
viraj124 (sponsor) acknowledged
#3 - 0xA5DF
2023-12-16T07:19:30Z
L
R
I
R
L
R
R
NC
I
NC
NC
#4 - 0xA5DF
2023-12-16T07:21:39Z
Side note: regarding layout I'd recommend adding a direct link to the relevant code (including line number), this way it's easier to see the context of the finding
#5 - 0xA5DF
2023-12-16T08:00:14Z
+1 low from #299
#6 - c4-judge
2023-12-16T12:05:04Z
0xA5DF marked the issue as grade-a