-
Notifications
You must be signed in to change notification settings - Fork 7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Truncating decimals in _convertDecimals()
causes ERC20 token value loss
#156
Comments
raymondfam marked the issue as insufficient quality report |
raymondfam marked the issue as duplicate of #135 |
0xA5DF marked the issue as unsatisfactory: |
Hi @0xA5DF! Thank you for your judgment. I would also remark that my issue seems not a duplicate of #135. It suggests to remove dust, while i'm suggesting to manage dust also inside OceanAdapter contract. Thanks in advance for your work |
Hey @niser93 |
Thank you for the answer. My POC is based on a token that has 32 decimals just to stress the importance of deviation. The problem is valid if the token uses more than 18 decimals, for example, 20 decimals. An example can be YAMv2, with 24 decimals, which seems not used anymore. Other example is NEAR token on Ethereum, with 24 decimals. According to OpenZeppelin ERC20 documentation:
For this reason, all tokens commonly used have 18 decimals. Furthermore, many topics suggest developers use 18 decimals. However, Ocean aims to manage ERC20 with any number of decimals. Due to the purpose of the protocol, it should be invariant with respect to token decimals. In contest invariant section
There were proposals to standardize 18-decimal tokens (see this). However, to date ERC20 token with more than 18 decimals belongs to standard, and without different specifications on the contest page, I think this issue could be considered as a deviation from the expected behavior. |
Got it, taking another look at the issue it seems that even in those cases the amount would be insignificant. |
Yes, it will be insignificant amount per interactions. But Ocean gives the possibility to perform hundreds, or thousands of interactions using |
Even with a billion interactions, 1e-15 times 1 billion would be 1e-6 USD. Does that seem like a significant amount to you? |
I'm not able to estimate how many interactions will be done. I agree that they should be at least 10e20 to have a significant losses. I would like to point you attention on the fact that convertDecimal() is inside OceanAdapter, and this abstract contract will be inherited by every curve adapter. So many pools and other kind of contracts could be built on top of this primitive. Curve2PoolAdapter and CurveTricryptoAdapter are just a basic bricks on top of that it can built many applications. So it could be very hard estimate how many interactions can be affected. |
Even with a trillion transactions it would still not be significant, and I think it's safe to say there aren't going to be more than a trillion txs in the foreseeable future |
Ok! I absolutely accept your judgement. Thank you for your time and your clarifications |
No problem |
Lines of code
https://github.com/code-423n4/2023-11-shellprotocol/blob/485de7383cdf88284ee6bcf2926fb7c19e9fb257/src/adapters/OceanAdapter.sol#L138-L159
Vulnerability details
Impact
An erroneous implementation of decimals conversion inside OceanAdapter.sol cuts less significant digits of
ERC20
tokens that use more than 18 digits. This leads to an unjustified loss of value during every type of ComputeType action.Furthermore, we didn't find any restriction on token decimals, and we assume that every ERC20-compliant contract could be implemented and used inside Ocean. In fact, OceanAdapter.
_convertDecimals()
is written to manage both cases:decimalsFrom < decimalsTo
anddecimalsFrom > decimalsTo
. We want also underlying that tests of this function are not exhaustive, because they only checkusdc->usdt
andusdt->usdc
swap (both use 6 decimals).Finally, referring to Can an
ERC-20
have more than 18 decimals?, we should assume that anERC20
implementation could use at most 77 decimals. Even if this implementation may be problematic, it is possible. In a more likely future implementation of someERC20
token, could be possible to use anERC20
with 32 decimals, for example.Proof of Concept
This issue is present both in Curve2PoolAdapter.sol and CurveTricryptoAdapter.sol. We are going to continue PoC with the former, but similar considerations can be applied on the latter.
In Curve2PoolAdapter.sol#L142-L184 there is
primitiveOutputAmount()
function. This function is called from Ocean contract in_executeInteraction()
when it receivesCurve2PoolAdapter.sol
address asexternalContract
parameter. This isprimitiveOutputAmount()
function:Assuming we are performing a swap operation. Furthermore, let's assume that
inputToken
andoutputToken
are tokens ERC20 compliant with 32 decimals. At line 173, after swapping, contract call_convertDecimals()
on this token, passingdecimals[outputToken]
that we assume is 32 andNORMALIZED_DECIMALS
, i.e. 18.Now, let's analyze _convertDecimals() function, inside
OceanAdapter.sol
:In our case,
decimalsFrom = 32
anddecimalsTo = 18
, so function will execute this portion of code:This will cut digits that are less significant than 10^15. So after swapping, the user loses several decimal amounts without any reason. Even if it could be a small amount (if we are thinking about a token with 32 decimals, it will be comparable with a gwei), enough amount of interactions inside
_doMultipleInteraction
could lead to a big loss. For this reason, we think this issue should be high.Tools Used
Visual inspection
Recommended Mitigation Steps
It could be better that before normalizing, the amount lost thanks to conversion is sent to the user or accumulated in a pool.
Assessed type
Decimal
The text was updated successfully, but these errors were encountered: