Platform: Code4rena
Start Date: 04/01/2022
Pot Size: $75,000 USDC
Total HM: 17
Participants: 33
Period: 7 days
Judge: 0xean
Total Solo HM: 14
Id: 74
League: ETH
Rank: 3/33
Findings: 6
Award: $7,476.43
🌟 Selected for report: 6
🚀 Solo Findings: 4
🌟 Selected for report: sirhashalot
1703.8454 USDC - $1,703.85
sirhashalot
The CollateralizedDebt.sol contract is a ERC721 token. It has a mint()
function, which uses the underlying safeMint()
function to create an ERC721 token representing a collateral position. The burn()
function in CollateralizedDebt.sol should reverse the actions of mint()
by burning the ERC721 token, but the ERC721 _burn()
function is never called. This means a user can continue to hold their ERC721 token representing their position after receiving their funds. This is unlike the burn()
function found in Bond.sol, Insurance.sol, and Liquidity.sol, which all call the _burn()
function (though note the _burn()
function in these other Timeswap Convenience contracts is the ERC20 _burn()
).
The problematic burn()
function is found in CollareralizedDebt.sol
https://github.com/code-423n4/2022-01-timeswap/blob/bf50d2a8bb93a5571f35f96bd74af54d9c92a210/Timeswap/Timeswap-V1-Convenience/contracts/CollateralizedDebt.sol#L80-L88
Compare this function to the burn()
functions defined in the other Timeswap Convenience contracts, which contain calls to _burn()
Include the following line in the burn()
function
_burn(id);
#0 - Mathepreneur
2022-01-15T22:39:08Z
If decided not to burn the ERC721 token at all. The burn in this context is burning the debt and collateral locked balance in the ERC721 token.
🌟 Selected for report: sirhashalot
1703.8454 USDC - $1,703.85
sirhashalot
The safeDecimals()
function, found in the SafeMetadata.sol contract and called in 3 different Timeswap Convenience contracts, can cause a revert. This is because the safeDecimals function attempts to use abi.decode to return a uint8 when data.length >= 32
. However, a data.length value greater than 32 will cause abi.decode to revert.
A similar issue was found in a previoud code4rena contest: https://github.com/code-423n4/2021-05-nftx-findings/issues/46
The root cause is line 28 of the safeDecimals()
function in SafeMetadata.sol
The following link shows the safeDecimals()
function in the BoringCrypto library, which might be where this code was borrowed from, uses the strict equality check data.length == 32
https://github.com/boringcrypto/BoringSolidity/blob/ccb743d4c3363ca37491b87c6c9b24b1f5fa25dc/contracts/libraries/BoringERC20.sol#L54
safeDecimals()
is used in multiple functions such as
Modify the safeDecimals()
function to change >= 32 to == 32 like this
if (success && data.length == 32) return abi.decode(data, (uint8));
#0 - Mathepreneur
2022-01-17T16:33:36Z
🌟 Selected for report: sirhashalot
1703.8454 USDC - $1,703.85
sirhashalot
The safeName()
function, found in the SafeMetadata.sol contract and called in 4 Timeswap Convenience contracts in the name()
functions, can cause a revert. This could make the 4 contracts not compliant with the ERC20 standard for certain asset pairs, because the name()
function should return a string and not revert.
The root cause of the issue is that the safeName()
function assumes the return type of any ERC20 token to be a string. If the return value is not a string, abi.decode() will revert, and this will cause the name()
functions in the Timeswap ERC20 contracts to revert. There are some tokens that aren't compliant, such as Sai from Maker, which returns a bytes32 value:
https://kauri.io/#single/dai-token-guide-for-developers/#token-info
Because this is known to cause issues with tokens that don't fully follow the ERC20 spec, the safeName()
function in the BoringCrypto library has a fix for this. The BoringCrypto safeName()
function is similar to the one in Timeswap but it has a returnDataToString()
function that handles the case of a bytes32 return value for a token name:
https://github.com/boringcrypto/BoringSolidity/blob/ccb743d4c3363ca37491b87c6c9b24b1f5fa25dc/contracts/libraries/BoringERC20.sol#L15-L47
The root cause is line 12 of the safeName()
function in SafeMetadata.sol
The safeName()
function is called in:
Use the BoringCrypto safeName()
function code to handle the case of a bytes32 return value:
https://github.com/boringcrypto/BoringSolidity/blob/ccb743d4c3363ca37491b87c6c9b24b1f5fa25dc/contracts/libraries/BoringERC20.sol#L15-L47
#0 - Mathepreneur
2022-01-17T16:30:48Z
🌟 Selected for report: sirhashalot
1703.8454 USDC - $1,703.85
sirhashalot
The safeSymbol()
function, found in the SafeMetadata.sol contract and called in 4 Timeswap Convenience contracts in the symbol()
functions, can cause a revert. This could make the 4 contracts not compliant with the ERC20 standard for certain asset pairs, because the symbol()
function should return a string and not revert.
The root cause of the issue is that the safeSymbol()
function assumes the return type of any ERC20 token to be a string. If the return value is not a string, abi.decode() will revert, and this will cause the symbol()
functions in the Timeswap ERC20 contracts to revert.
Because this is known to cause issues with tokens that don't fully follow the ERC20 spec, the safeSymbol()
function in the BoringCrypto library has a fix for this. The BoringCrypto safeSymbol()
function is similar to the one in Timeswap but it has a returnDataToString()
function that handles the case of a bytes32 return value for a token name:
https://github.com/boringcrypto/BoringSolidity/blob/ccb743d4c3363ca37491b87c6c9b24b1f5fa25dc/contracts/libraries/BoringERC20.sol#L15-L39
The root cause is line 20 of the safeSymbol()
function in SafeMetadata.sol
The safeSymbol()
function is called in:
Use the BoringCrypto safeSymbol()
function code with the returnDataToString()
parsing function to handle the case of a bytes32 return value:
https://github.com/boringcrypto/BoringSolidity/blob/ccb743d4c3363ca37491b87c6c9b24b1f5fa25dc/contracts/libraries/BoringERC20.sol#L15-L39
#0 - Mathepreneur
2022-01-17T16:29:08Z
🌟 Selected for report: sirhashalot
567.9485 USDC - $567.95
sirhashalot
The IPair.sol interface has a comment incorrectly referencing UQ0.40 format for a uint16 return value. A UQ0.40 return value would require a uint40 or larger to be stored, so the comment should state UQ0.16 if a uint16 value is returned. https://en.wikipedia.org/wiki/Q_(number_format)#Characteristics
UQ0.40 is used incorrectly in two interface contracts: https://github.com/code-423n4/2022-01-timeswap/blob/bf50d2a8bb93a5571f35f96bd74af54d9c92a210/Timeswap/Timeswap-V1-Core/contracts/interfaces/IPair.sol#L179 https://github.com/code-423n4/2022-01-timeswap/blob/bf50d2a8bb93a5571f35f96bd74af54d9c92a210/Timeswap/Timeswap-V1-Core/contracts/interfaces/IFactory.sol#L39
Change UQ0.40 to UQ0.16 in the code comment
#0 - Mathepreneur
2022-01-15T22:45:49Z
The protocol fee is a very small number, since it gets multiplied by the duration of the transaction (seconds). So it is correct that it it UQ0.40, we only need the last 16 decimal places of the UQ0.40. I will add more explanation in the comments about this.
🌟 Selected for report: sirhashalot
93.0809 USDC - $93.08
sirhashalot
The createPair()
function in TimeswapFactory.sol passes a salt value to when creating a new TimeswapPair. This salt is never used. Most likely the salt is an artifact from Uniswap V2 Core code. However, Uniswap v2 Core only uses a salt for the CREATE2 assembly call, which is not used in the Timeswap project.
https://docs.uniswap.org/protocol/V2/guides/smart-contract-integration/getting-pair-addresses
The unnecessary salt is found at: https://github.com/code-423n4/2022-01-timeswap/blob/bf50d2a8bb93a5571f35f96bd74af54d9c92a210/Timeswap/Timeswap-V1-Core/contracts/TimeswapFactory.sol#L52
Don't pass a salt when creating a pair. The revised line of code should look like:
pair = new TimeswapPair(asset, collateral, fee, protocolFee);
#0 - Mathepreneur
2022-01-18T16:24:54Z
It is currently not in used, but a different convenience contract or proxy contract might find this feature useful.