Platform: Code4rena
Start Date: 30/09/2021
Pot Size: $75,000 ETH
Total HM: 9
Participants: 15
Period: 7 days
Judge: 0xean
Total Solo HM: 2
Id: 39
League: ETH
Rank: 8/15
Findings: 3
Award: $2,702.21
π Selected for report: 3
π Solo Findings: 0
loop
ERC20.transfer
and ERC20.transferFrom
are used for transfers of underlying tokens. The return value of transfer and transferFrom are not checked when used.
The same goes for using ERC20.approve
for approving the underlying tokens.
Several tokens do not revert in case of failure and return false. If one of these tokens is used as underlying the transfer might not have actually been performed and still count as a correct transfer as it returned false but wasn't checked.
Slither reference: https://github.com/crytic/slither/wiki/Detector-Documentation#unchecked-transfer Similar issue in another c4 contest: https://github.com/code-423n4/2021-08-yield-findings/issues/31
Lines with transfer/transferFrom:
Lines with approve:
Slither
Use the SafeERC20
library with safeTransfer
, safeTransferFrom
and safeApprove
instead.
#0 - 0xean
2021-10-16T23:06:37Z
dupe of #155
π Selected for report: loop
0.3306 ETH - $980.03
loop
Line 124 of Swivel.sol describes the parameter uint256 a
, but has wrong parameter name:
/// @param o Amount of volume (principal) being filled by the taker's exit
No direct impact apart from code readability
Code snippet for function declaration + spec:
/// @notice Allows a user to initiate a zcToken by filling an offline vault initiate order /// @dev This method should pass (underlying, maturity, sender, maker, a) to MarketPlace.custodialInitiate /// @param o Order being filled /// @param o Amount of volume (principal) being filled by the taker's exit /// @param c Components of a valid ECDSA signature function initiateZcTokenFillingVaultInitiate(Hash.Order calldata o, uint256 a, Sig.Components calldata c) internal
Change the second o
to a
#0 - JTraversa
2021-10-07T15:28:50Z
Definitely non-critical :)
#1 - 0xean
2021-10-16T20:23:24Z
1 β Low: Low: Assets are not at risk. State handling, function incorrect as to spec, issues with comments.
Leaving as 1
#2 - JTraversa
2021-11-03T20:58:09Z
loop
The following functions are declared as public but can be external:
createMarket
(https://github.com/Swivel-Finance/gost/blob/v2/test/marketplace/MarketPlace.sol#L53-L70)transferVaultNotional
(https://github.com/Swivel-Finance/gost/blob/v2/test/marketplace/MarketPlace.sol#L234-L238)transferVaultNotionalFee
(https://github.com/Swivel-Finance/gost/blob/v2/test/marketplace/MarketPlace.sol#L245-L248)balancesOf
(https://github.com/Swivel-Finance/gost/blob/v2/test/vaulttracker/VaultTracker.sol#L243-L245)Slither reference: https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external
Slither
0.0171 ETH - $50.84
loop
uint16[] public fenominator;
on line 24 of Swivel.sol is currently a dynamic size array. It does however only hold 4 values ([zcTokenInitiate, zcTokenExit, vaultInitiate, vaultExit]
).
These are also the only values used in the initiate and exit functions where they use the respective fenominator at index 0-3 to calculate the fee.
Can be changed into a fixed array size with 4 elements to save a small amount of gas when contract gets deployed and array interacted with.
Change array declaration to:
uint16[4] public fenominator;
#0 - 0xean
2021-10-16T22:13:38Z
dupe of #114
loop
When calculating principalFilled
or premiumFilled
and fee
amounts in the exit and initiate functions the 2 1e18 values cancel each other out.
Adds unnecessary operations to calculating the principal-premium-fee calculation.
When selling nTokens principalFilled
is used:
uint256 principalFilled = (((a * 1e18) / o.premium) * o.principal) / 1e18; uint256 fee = ((principalFilled * 1e18) / fenominator[x]) / 1e18;
When buying nTokens premiumFilled
is used:
uint256 premiumFilled = (((a * 1e18) / o.principal) * o.premium) / 1e18; uint256 fee = ((premiumFilled * 1e18) / fenominator[x]) / 1e18;
Note: x is the relevant fenominator
used for the different initiate and exit functions.
Both calculations are however similar and in both cases the multiplication by 1e18 at the start and division by 1e18 at the end cancel each other out unless I missed an edge-case. When testing removing the 1e18's from the calculation always gave the same result at a lower gas price.
Lines where calculation is used:
Remix
π Selected for report: loop
0.0941 ETH - $278.98
loop
On lines 17 & 18 the following string literals are declared:
string constant public NAME = 'Swivel Finance';
string constant public VERSION = '2.0.0';
bytes32 constant
can be used in stead of string constant
to slightly reduce gas cost as neither of the two strings are larger than 32 bytes.
As a result NAME
and VERSION
can also be assigned in smaller bytes packing it together with HOLD
like this:
bytes16 constant public NAME = 'Swivel Finance'; bytes8 constant public VERSION = '2.0.0'; uint64 constant public HOLD = 259200;
Used following contract code in remix to compare cost:
pragma solidity 0.8.4; contract Test { string constant public NAME = 'Swivel Finance'; string constant public VERSION = '2.0.0'; bytes32 constant public NAME32 = 'Swivel Finance'; bytes32 constant public VERSION32 = '2.0.0'; uint256 constant public HOLD = 259200; bytes16 constant public NAME16 = 'Swivel Finance'; bytes8 constant public VERSION8 = '2.0.0'; uint64 constant public HOLD64 = 259200; }
Remix
#0 - JTraversa
2021-10-07T15:52:06Z
The judges may have run into this before. The EIP-712 standard expects name & version as strings?
Unsure if non-issue for that reason.
#1 - 0xean
2021-10-16T18:44:52Z
The gas savings for a one time deployment of the contract is probably trivial, but yes, this would work.
EIP 712 should also work (with some changes) with the bytes format since the encoding is identical
#2 - JTraversa
2021-10-24T20:56:06Z
We decided to just leave it as is for clarity across our stack. Just wasnt worth the tiny gas savings for deployment vs ensuring correctness / needing to update multiple levels of signature validation.
π Selected for report: loop
0.3306 ETH - $980.03
loop
the parameter uint8 d
of the createMarket
function is lacking a parameter description in function spec.
No direct impact, but with the parameter naming scheme of only using the first letter of its description the parameter spec is essential.
Code snippet for funciton spec + declaration:
/// @notice Allows the owner to create new markets /// @param u Underlying token address associated with the new market /// @param m Maturity timestamp of the new market /// @param c cToken address associated with underlying for the new market /// @param n Name of the new zcToken market /// @param s Symbol of the new zcToken market function createMarket( address u, uint256 m, address c, string memory n, string memory s, uint8 d ) public onlyAdmin(admin) returns (bool)
Add parameter spec for uint8 d
#0 - JTraversa
2021-10-07T15:29:29Z
non-critical
#1 - 0xean
2021-10-16T20:22:43Z
per the docs - issues with comments are a "1"
1 β Low: Low: Assets are not at risk. State handling, function incorrect as to spec, issues with comments.
#2 - JTraversa
2021-11-03T20:51:35Z
https://github.com/Swivel-Finance/gost/pull/174/commits/6c30c26f07b125ab4dac0ce42d793b55288bc0d9
removed decimals as a param and now read directly from underlying.
cToken input -> underlying -> decimals