88mph
Findings & Analysis Report
2021-06-14
Table of contents
- Summary
- Scope
- Severity Criteria
- High Risk Findings
-
- [N-01] zero amount of token value can be entered for creating vest object in vesting.sol
- [N-02] lack of zero address validation in constructor
- [N-03] Multiple definitions of PRECISION
- [N-04] contract AaveMarket function setRewards has a misleading revert message
- [N-05] Mismatch between the comment and the actual code
- Disclosures
Overview
About C4
Code 432n4 (C4) is an open organization consisting of security researchers, auditors, developers, and individuals with domain expertise in smart contracts.
A C4 code contest is an event in which community participants, referred to as Wardens, review, audit, or analyze smart contract logic in exchange for a bounty provided by sponsoring projects.
During the code contest outlined in this document, C4 conducted an analysis of 88mph’s smart contract system written in Solidity. The code contest took place between May 13 and May 19, 2021.
Wardens
5 Wardens contributed reports to the 88mph code contest:
This contest was judged by ghoul.sol.
Final report assembled by ninek.
Summary
The C4 analysis yielded an aggregated total of 14 unique vulnerabilities. All of the issues presented here are linked back to their original finding.
Of these vulnerabilities, 2 received a risk rating in the category of MEDIUM severity, and 5 received a risk rating in the category of LOW severity.
C4 analysis also identified 7 non-critical recommendations.
Scope
The code under review can be found within the C4 code contest repository and comprises 39 smart contracts written in the Solidity programming language.
Severity Criteria
C4 assesses the severity of disclosed vulnerabilities according to a methodology based on OWASP standards.
Vulnerabilities are divided into three primary risk categories: high, medium, and low.
High-level considerations for vulnerabilities span the following key areas when conducting assessments:
- Malicious Input Handling
- Escalation of privileges
- Arithmetic
- Gas use
Further information regarding the severity criteria referenced throughout the submission review process, please refer to the documentation provided on the C4 website.
High Risk Findings
There were no high risk findings identified in this contest.
Medium Risk Findings
[M-01] Incompatability with deflationary / fee-on-transfer tokens
The DInterest.deposit
function takes a depositAmount
parameter but this parameter is not the actual transferred amount for fee-on-transfer / deflationary (or other rebasing) tokens.
The actual deposited amount might be lower than the specified depositAmount
of the function parameter.
This would lead to wrong interest rate calculations on the principal.
Recommend transferring the tokens first and comparing pre-/after token balances to compute the actual deposited amount.
ZeframLou (88mph) acknowledged:
While this is true, we have no plans to support fee-on-transfer or rebasing tokens.
[M-02] Unchecking the ownership of mph
in function distributeFundingRewards
could cause several critical functions to revert
In contract MPHMinter
, the function distributeFundingRewards
does not check whether the contract itself is the owner of mph
. If the contract is not the owner of mph
, mph.ownerMint
could revert, causing functions such as withdraw
, rolloverDeposit
, payInterestToFunders
in the contract DInterest
to revert as well.
Recommend adding a mph.owner() != address(this)
check as in the other functions (e.g., mintVested
).
Fixed in this commit.
Low Risk Findings
[L-01] Use openzeppelin ECDA for erecover
In Sponsorable.sol
is using erecover directly to verify the signature.
Since it is a critical piece of the protocol, it is recommended to use the ECDSA from openzeppelin as it does more validations when verifying the signature.
Fixed in this commit.
[L-02] Anyone can withdraw vested amount on behalf of someone
The Vesting.withdrawVested
function allows withdrawing the tokens of other users.
While the tokens are sent to the correct address, this can lead to issues with smart contracts that might rely on claiming the tokens themselves.
As one example, suppose the _to
address corresponds to a smart contract that has a function of the following form:
function withdrawAndDoSomething() {
contract.withdrawVested(address(this), amount);
token.transfer(externalWallet, amount)
}
If the contract has no other functions to transfer out funds, they may be locked forever in this contract.
Recommend not allowing users to withdraw on behalf of other users.
ZeframLou (88mph) acknowledged:
This is true, but
Vesting.sol
is only kept for legacy support,Vesting02.sol
will be the main vesting contract, so we’re fine with this.
[L-03] Extra precautions in updateAndQuery
The function updateAndQuery
of EMAOracle.sol subtracts the incomeIndex
with the previous incomeIndex
.
These incomeIndex
values are retrieved via the moneyMarket contract from an external contract.
If, by accident, the previous incomeIndex
is larger than the current incomeIndex
, then the subtraction would be negative and the code halts (reverts) without an error message.
Also, the updateAndQuery
function would not be able to execute (until the current incomeIndex
is larger than the previous incomeIndex
).
This situation could occur when an error occurs in one of the current or future money markets.
Recommend giving an error message when the previous incomeIndex
is larger than the current incomeIndex
and/or create a way to recover from this erroneous situation.
Fixed in this commit.
[L-04] Add extra error message in_depositRecordData
In the function _depositRecordData
of DInterest.sol, interestAmount
is lowered with feeAmount
.
If, by accident, feeAmount
happens to be larger than interestAmount
, an error occurs and the execution stops, without an error message.
This might make troubleshooting this situation more complicated.
Suggest adding something like:
require(interestAmount >= feeAmount,"DInterest: fee too large");
ZeframLou (88mph) acknowledged
[L-05] function payInterestToFunders does not have a re-entrancy modifier
Function payInterestToFunders
does not have a re-entrancy modifier. I expect to see this modifier because similar functions (including sponsored version) have it.
Add ‘nonReentrant’ to function payInterestToFunders
.
Fixed in this commit.
Non-Critical Findings
[N-01] zero amount of token value can be entered for creating vest object in vesting.sol
The impact will be on the end-user; if they entered the zero amount by mistake, they would end paying a gas fee for nothing.
In the vest
function, there is checking of vestPeriodInSeconds
but no checking of amount, due to which amount can be set to 0.
Recommend adding a condition to check for function parameter.
ZeframLou (88mph) acknowledged:
We’re ok with this.
Passing 0 doesn’t break anything. This is a non-critical issue.
[N-02] lack of zero address validation in constructor
Due to the lack of zero address validation, there is a chance of losing funds.
Recommend adding zero address check.
ZeframLou (88mph) acknowledged:
We’re fine with this.
[N-03] Multiple definitions of PRECISION
There are multiple definitions of PRECISION.
This risk is that is someone (a new developer?) would change the value of PRECISION on one location and might forget to change it in one of the other places. Also, 2 of them are defined as public while the rest is internal.
Recommend defining PRECISION once and import this in all the other contracts.
ZeframLou (88mph) acknowledged
[N-04] contract AaveMarket function setRewards has a misleading revert message
AaveMarket function setRewards
has a misleading revert message:
require(newValue.isContract(), "HarvestMarket: not contract");
Recommend naming it ‘AaveMarket’, not ‘HarvestMarket’.
Fixed in this commit.
[N-05] Mismatch between the comment and the actual code
Here the comment says that it should transfer from msg.sender, but it actually transfers from the sender which is not always the msg.sender (e.g., sponsored txs):
// Transfer `fundAmount` stablecoins from msg.sender
stablecoin.safeTransferFrom(sender, address(this), fundAmount);
Update the comment to match the code.
Fixed in this commit.
Gas Optimizations
[G-01] Gas optimizations by using external over public
Some functions could use external instead of public in order to save gas.
If we run the following methods on Remix, we can see the difference:
// transaction cost 21448 gas
// execution cost 176 gas
function tt() external returns(uint256) {
return 0;
}
// transaction cost 21558 gas
// execution cost 286 gas
function tt_public() public returns(uint256) {
return 0;
}
Many of the functions listed are also called within the contract, so changing their visibility to
public
will break things.
Even though out of the whole list, only a few functions are good candidates to be changed, it’s technically a valid suggestion.
Addressed in this commit.
[G-02] Gas optimizations - storage over memory
Some functions are using memory to read state variables when using storage is more gas efficient.
The memory location of those variables is intentionally set to be
memory
to only load the data from storage once to save gas. If changed tostorage
, there will be multiple `SLOAD’s for accessing the same variable, which is expensive.
In case of the
withdrawableAmountOfDeposit
function, the warden is right._getDeposit
makes 7SLOAD
calls in this case because it’s pulling the whole object from storage. Using storage would make only 5SLOAD
calls.In the case of
_getVestWithdrawableAmount
, either way uses 6SLOAD
calls.Warden is correct in at least one case, so the reports stand.
Addressed in this commit.
Disclosures
C4 is an open organization governed by participants in the community.
C4 Contests incentivize the discovery of exploits, vulnerabilities, and bugs in smart contracts. Security researchers are rewarded at an increasing rate for finding higher-risk issues. Contest submissions are judged by a knowledgeable security researcher and solidity developer and disclosed to sponsoring developers. C4 does not conduct formal verification regarding the provided code but instead provides final verification.
C4 does not provide any guarantee or warranty regarding the security of this project. All smart contract software should be used at the sole risk and responsibility of users.