Platform: Code4rena
Start Date: 16/11/2021
Pot Size: $50,000 ETH
Total HM: 11
Participants: 17
Period: 7 days
Judge: LSDan
Total Solo HM: 8
Id: 49
League: ETH
Rank: 2/17
Findings: 6
Award: $8,098.70
π Selected for report: 10
π Solo Findings: 2
0.4476 ETH - $2,068.07
pauliax
Overlay uses OZ contracts version 4.3.2:
dependencies: - OpenZeppelin/openzeppelin-contracts@4.3.2
and has a contract that inherits from ERC1155Supply:
contract OverlayV1OVLCollateral is ERC1155Supply
This version has a recently discovered vulnerability: https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-wmpv-c2jp-j2xg
In your case, function unwind relies on totalSupply when calculating _userNotional, _userDebt, _userCost, and _userOi, so a malicious actor can exploit this vulnerability by first calling 'build' and then on callback 'unwind' in the same transaction before the total supply is updated.
Consider updating to a patched version of 4.3.3.
π Selected for report: pauliax
pauliax
There are contracts that contain functions that change important parameters of the system, e.g. OverlayV1Mothership has setOVL, initializeMarket, disableMarket, enableMarket, initializeCollateral, enableCollateral, disableCollateral, adjustGlobalParams. None of these functions emit events, nor they are timelocked. Usually, it is a good practice to give time for users to react and adjust to changes.
A similar issue was submitted in a previous contest and assigned a severity of Medium: https://github.com/code-423n4/2021-09-swivel-findings/issues/101
Consider using a timelock for critical params of the system and emitting events to inform the outside world.
#0 - commercium-sys
2021-11-24T17:45:38Z
The plan has been to have a timelock at some point in the protocol. Probably on whatever is the admin for the mothership. But this just had to be evaluated. It might be on the market contract itself, or on the addresses granted the role of admin.
#1 - mikeyrf
2021-12-07T14:31:00Z
duplicate #64
#2 - dmvt
2021-12-18T13:45:56Z
I'm removing the duplicate in this case because issue #64 refers exclusively to the events. This issue is focused primarily on the lack of governance timelock, which has traditionally been considered a medium severity issue.
π Selected for report: pauliax
0.4973 ETH - $2,297.86
pauliax
contract OverlayV1OVLCollateral and OverlayV1Governance cache ovl address:
IOverlayTokenNew immutable public ovl;
This variable is initialized in the constructor and fetched from the mothership contract:
mothership = IOverlayV1Mothership(_mothership); ovl = IOverlayV1Mothership(_mothership).ovl();
ovl is declared as immutable and later contract interacts with this cached version. However, mothership contains a setter function, so the governor can point it to a new address:
function setOVL (address _ovl) external onlyGovernor { ovl = _ovl; }
OverlayV1OVLCollateral and OverlayV1Governance will still use this old cached value.
Consider if this was intended, or you want to remove this cached version and always fetch on the go (this will increase the gas costs though).
#0 - commercium-sys
2021-11-24T17:30:13Z
This is just a detail we were yet to settle on but definitely were going to as we got the contracts to a totally deployable state.
#1 - mikeyrf
2021-12-08T22:48:56Z
disagree w severity reason - would put this at 1 - Low Risk given the governor would be responsible for properly setting
#2 - dmvt
2021-12-20T19:40:36Z
I agree with the warden that this constitutes a medium risk.
From the judging criteria (emphasis mine):
2 β Med (M): vulns have a risk of 2 and are considered βMediumβ severity when assets are not at direct risk, but the function of the protocol or its availability could be impacted, or leak value with a hypothetical attack path with stated assumptions, but external requirements.
pauliax
OverlayV1Mothership has declared variables named MIN_FEE and MAX_FEE, or MIN_MARGIN_MAINTENANCE and MAX_MARGIN_MAINTENANCE, however, none of these variables are used anywhere, e.g. I expected to see a fee validated with these min/max boundaries, but now governance can set any arbitrary value.
Consider either using these variables where they were intended or removing them to reduce the confusion.
#0 - commercium-sys
2021-11-24T17:48:26Z
I wouldn't call this much of a bug, quite possibly something we would have included. At any rate, it is on us to get the parameterization of the contracts correct and we know this.
#1 - mikeyrf
2021-12-06T23:37:29Z
duplicate #77 - bounds on governance params
π Selected for report: pauliax
Also found by: Meta0xNull, pants, ye0lde
pauliax
There are TODOs left in the code. While this does not cause any direct issue, it indicates a bad smell and uncertainty. In previous reports, such submissions were assigned a score of 'low' so I think it's a fair game to submit this as an issue here also. Reference: https://github.com/code-423n4/2021-09-swivel-findings/issues/67 https://github.com/code-423n4/2021-10-tempus-findings/issues/39
Also, there are some misleading comments, e.g.:
/// @notice Internal update function to price, cap, and pay funding. function update () public virtual returns (
the comment says that function is internal but it is actually declared as public.
Consider implementing or removing TODOs and updating misleading comments.
#0 - commercium-sys
2021-11-24T17:49:09Z
I wouldn't call these things a bug, they have just been waiting for house cleaning when we get the contracts to a final, really going to deploy state.
π Selected for report: pauliax
0.1658 ETH - $765.95
pauliax
IOverlayV1Market declares a function adjustParams but OverlayV1Market does not have such a function. There may be more examples where interface and contract differ.
Consider explicitly inheriting an interface to enforce compile-time check:
contract OverlayV1Market is IOverlayV1Market
#0 - commercium-sys
2021-12-08T22:37:32Z
We hadn't reached the point where we were going to make sure a number of things about the contract, including absolute adherence between the interfaces and the contracts. So we acknowledge.
0.0029 ETH - $13.54
pauliax
Unused state variables or functions can be removed to save some deployment gas. Examples of such code are:
uint16 public constant MIN_FEE = 1; // 0.01% uint16 public constant MAX_FEE = 100; // 1.00% uint16 public constant MIN_MARGIN_MAINTENANCE = 100; // 1% maintenance uint16 public constant MAX_MARGIN_MAINTENANCE = 6000; // 60% maintenance bytes32 public constant MINTER = keccak256("MINTER"); bytes32 public constant BURNER = keccak256("BURNER"); modifier onlyGuardian event log(string k, uint v); event log_addr(string k, address v); uint256 public leverageMax;
#0 - commercium-sys
2021-11-24T17:43:47Z
Yeah this is just languishing there until we get to the point of finalizing the contracts. Not much of a bug in my opinion though.
π Selected for report: pauliax
0.0161 ETH - $74.28
pauliax
There are places where the same storage variable is accessed multiple times in the same function. It would be more gas efficient to cache these variables and re-use them where necessary. E.g. functions initializeCollateral, enableCollateral, disableCollateral access ovl 4 times, function fetchPricePoint accesses macroWindow 4 times and microWindow 3 times. There are many more places where repeated storage access can be cached.
#0 - commercium-sys
2021-12-08T22:40:27Z
We are not very interested in saving gas on governance functions that will only be used once in a blue moon. Furthermore, micro and macro window are immutables - so there is no read cost for them.
#1 - dmvt
2021-12-21T14:10:26Z
Whether you want to save gas here or not, the report is valid.
π Selected for report: pauliax
0.0161 ETH - $74.28
pauliax
Before:
require(currentAllowance >= amount + burnt, "OVL:allowance<amount+burnt"); unchecked { _approve(sender, msg.sender, currentAllowance - amount - burnt); }
After:
uint totalAmount = amount + burnt; require(currentAllowance >= totalAmount, "OVL:allowance<amount+burnt"); unchecked { _approve(sender, msg.sender, currentAllowance - totalAmount); }
This approach can be applied in several functions, e.g. transferFromBurn, _transferBurn.
#0 - commercium-sys
2021-11-24T17:42:17Z
Super tiny savings here.
π Selected for report: pauliax
0.0161 ETH - $74.28
pauliax
Can eliminate the subtraction here by substituting the order of operations:
positions.push(Position.Info({ market: _market, isLong: _isLong, leverage: _leverage, pricePoint: _pricePointNext, oiShares: 0, debt: 0, cost: 0 })); positionId_ = positions.length - 1;
After:
positionId_ = positions.length; positions.push...
#0 - commercium-sys
2021-11-24T17:41:42Z
Super tiny gas savings there - maybe 20 gas? Less?
0.0072 ETH - $33.42
pauliax
There are some structs that could be optimized for storage space. Struct variables are stored in 32 bytes each so you can group smaller types to occupy less storage, e.g. I don't think that leverage or pricePoint need to occupy uint256.
Consider using smaller types for values that are not expected to reach their limits. You can read more here: https://fravoll.github.io/solidity-patterns/tight_variable_packing.html or in the official documentation: https://docs.soliditylang.org/en/v0.4.21/miscellaneous.html
#0 - commercium-sys
2021-11-24T17:41:16Z
Yeah, this is something I have been intending on doing.
It would have been great to have seen a concrete idea on how to do this best.
I am currently considering using a bytes32 array to see how much we can pack in there, independently of the types provided by Solidity.
pauliax
Assigned operations to constant variables are re-evaluated every time. See https://github.com/ethereum/solidity/issues/9232
bytes32 public constant GOVERNOR = keccak256("GOVERNOR"); bytes32 public constant GUARDIAN = keccak256("GUARDIAN"); bytes32 public constant MINTER = keccak256("MINTER"); bytes32 public constant BURNER = keccak256("BURNER");
Change 'constant' to 'immutable'.
#0 - commercium-sys
2021-11-24T17:43:56Z
Thanks, I did not know this.
#1 - mikeyrf
2021-12-08T22:38:56Z
duplicate #111