2021-05-88mph-findings's People
2021-05-88mph-findings's Issues
Incompatability with deflationary / fee-on-transfer tokens
Handle
cmichel
Vulnerability details
Vulnerability Details
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.
Impact
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.
Recommended Mitigation Steps
Transfer the tokens first and compare pre-/after token balances to compute the actual deposited amount.
Use openzeppelin ECDA for erecover
Handle
a_delamo
Vulnerability details
Impact
In Sponsorable.sol
is using erecover directly to verify the signature.
Being such a critical piece of the protocol, I would recommend using the ECDSA from openzeppelin as it does more validations when verifying the signature.
// Currently
address recoveredAddress =
ecrecover(digest, sponsorship.v, sponsorship.r, sponsorship.s);
require(
recoveredAddress != address(0) && recoveredAddress == sponsorship.sender,
"Sponsorable: invalid sig"
);
//ECDSA
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (281): 0 < s < secp256k1n ÷ 2 + 1, and for v in (282): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
require(uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0, "ECDSA: invalid signature 's' value");
require(v == 27 || v == 28, "ECDSA: invalid signature 'v' value");
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
require(signer != address(0), "ECDSA: invalid signature");
return signer;
}
Tools Used
None
Missmatch between the comment and the actual code
Handle
paulius.eth
Vulnerability details
Impact
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);
Recommended Mitigation Steps
Update the comment to match the code.
Extra precautions in updateAndQuery
Handle
gpersoon
Vulnerability details
Impact
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.
Proof of Concept
EMAOracle.sol:
function updateAndQuery() {
...
uint256 _lastIncomeIndex = lastIncomeIndex;
...
uint256 newIncomeIndex = moneyMarket.incomeIndex();
uint256 incomingValue =
(newIncomeIndex - _lastIncomeIndex).decdiv(_lastIncomeIndex) /
timeElapsed;
Tools Used
Editor
Recommended Mitigation Steps
Give an error message when the previous incomeIndex is larger than the current incomeIndex.
And/or create a way to recover from this erroneous situation.
Not reverting on failing ERC20 transfer
Handle
Sherlock
Vulnerability details
Impact
In the Visor.sol contract an IERC20 transferFrom() with an arbitrary token argument is used. The return value of the transferFrom() is not used, a boolean indicating if the transaction succeeded. Meaning the transaction could succeed without the actual transfer of tokens happening.
The new entry the time lock is valid even though the tokens are not transferred. User could drain tokens in the contract (e.g. from other users who's transfer did succeed)
Proof of Concept
Recommended Mitigation Steps
Use the OpenZeppelin SafeERC20 contract. Changing the call to safeTransferFrom(). This not only makes the transaction revert if the returned boolean is false. But also handles non-standard ERC20 tokens that don’t have boolean return values.
In Visor's case, I recommend using the TransferHelper.safeTransferFrom() as this library is used in other parts of the code.
Gas optimizations - Structs over multiple mappings
Handle
a_delamo
Vulnerability details
Impact
PercentageFeeModel.sol
is using multiple mappings for keeping track of the fees.
struct FeeOverride {
bool isOverridden;
uint256 fee;
}
mapping(address => FeeOverride) public interestFeeOverrideForPool;
mapping(address => FeeOverride) public earlyWithdrawFeeOverrideForPool;
mapping(address => mapping(uint256 => FeeOverride)) public interestFeeOverrideForDeposit;
mapping(address => mapping(uint256 => FeeOverride)) public earlyWithdrawFeeOverrideForDeposit;
I would recommend using a single mapping with a struct and inside of multiple mappings as is less gas efficient. Here more info: https://medium.com/@novablitz/storing-structs-is-costing-you-gas-774da988895e
Especially, given that some functions are currently accessing multiple mappings.
Also, if fees can not be zero. We could remove the FeeOverride
struct to just use uint, but I guess is not possible.
Double-checking 88mph form
Handle
sockdrawermoney
Vulnerability details
Impact
Detailed description of the impact of this finding.
Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
Tools Used
Recommended Mitigation Steps
Multiple definitions of PRECISION
Handle
gpersoon
Vulnerability details
Impact
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 on one of the other places.
Also 2 of them are defined public while the rest is internal.
Proof of Concept
DInterest.sol: uint256 internal constant PRECISION = 1018;
libs\DecMath.sol: uint256 internal constant PRECISION = 1018;
models\fee\PercentageFeeModel.sol: uint256 internal constant PRECISION = 1018;
models\interest\LinearDecayInterestModel.sol: uint256 public constant PRECISION = 1018;
models\interest\LinearInterestModel.sol: uint256 public constant PRECISION = 1018;
models\interest-oracle\EMAOracle.sol: uint256 internal constant PRECISION = 1018;
models\issuance\MPHIssuanceModel02.sol: uint256 internal constant PRECISION = 1018;
rewards\Vesting02.sol: uint256 internal constant PRECISION = 1018;
rewards\xMPH.sol: uint256 internal constant PRECISION = 10**18;
Tools Used
grep
Recommended Mitigation Steps
Define PRECISION once and import this in all the other contracts.
Add extra error message in_depositRecordData
Handle
gpersoon
Vulnerability details
Impact
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 difficult.
Proof of Concept
DInterest.sol:
function _depositRecordData(..) {
...
interestAmount -= feeAmount;
Tools Used
Editor
Recommended Mitigation Steps
Perhaps add something like:
require(interestAmount >= feeAmount,"DInterest: fee too large");
function payInterestToFunders does not have a re-entrancy modifier
Handle
paulius.eth
Vulnerability details
Impact
function payInterestToFunders does not have a re-entrancy modifier. I expect to see this modifier because similar functions (including sponsored version) have it.
Recommended Mitigation Steps
Add 'nonReentrant' to function payInterestToFunders.
zero amount of token value can be entered for creating vest object in vesting.sol
Handle
Jmukesh
Vulnerability details
Impact
impact will be on the end user , if they by mistake entered the zero amount , they will end paying gas fee for nothing
Proof of Concept
https://github.com/code-423n4/2021-05-88mph/blob/main/contracts/rewards/Vesting.sol#L24
in vest function there is checking of vestPeriodInSeconds but no checking of amount , due to which amount can be set to 0
Tools Used
manual review
Recommended Mitigation Steps
add condition, to check for function parameter
Use immutable keyword
Handle
gpersoon
Vulnerability details
Impact
In EMAOracle.sol several variables are initialized once and never changed. In the comments they are notified as immutable,
however the keyword immutable isn't used.
Proof of Concept
EMAOracle.sol:
contract EMAOracle is IInterestOracle, Initializable {
... /**
Immutable parameters
*/
uint256 public UPDATE_INTERVAL;
uint256 public UPDATE_MULTIPLIER;
uint256 public ONE_MINUS_UPDATE_MULTIPLIER;
function initialize(
..
UPDATE_INTERVAL = _updateInterval;
..
UPDATE_MULTIPLIER = updateMultiplier;
ONE_MINUS_UPDATE_MULTIPLIER = PRECISION - updateMultiplier;
Tools Used
Editor
Recommended Mitigation Steps
Make the variables that are only set once immutable
lack of input validation of timestamp in function _depositRecordData() , Dinternest.sol
Handle
Jmukesh
Vulnerability details
Impact
Due to lack of input validation in of timestamp _depositRecordData() , this function may get failed during execution
Proof of Concept
In Dinterest.sol
https://github.com/code-423n4/2021-05-88mph/blob/main/contracts/DInterest.sol#L796
function _depositRecordData(
address sender,
uint256 depositAmount,
uint256 maturationTimestamp
) internal virtual returns (uint256 depositID, uint256 interestAmount) {
// Ensure input is valid
require(
depositAmount >= MinDepositAmount,
"DInterest: Deposit amount too small"
);
uint256 depositPeriod = maturationTimestamp - block.timestamp;
require(
depositPeriod <= MaxDepositPeriod,
"DInterest: Deposit period too long"
);
here depositPeriod must be >=0 other wise , it will give error due to which function will not excute .
Tools Used
manual review
Recommended Mitigation Steps
add require condition for depositPeriod -->
require(depositPeriod >= 0)
Potential manipulation of `moneyMarket.incomeIndex`
Handle
cmichel
Vulnerability details
Vulnerability Details
The DInsterest
contract relies on moneyMarket.incomeIndex()
to return the accurate index.
For some implementations, like in yearn, this is the price per share token (vault.pricePerShare()
).
There might be cases where this price per share can be temporarily inflated/deflated, for example by depositing underlying tokens to the vault and claiming them back later via a rescue function, or by removing underlyings by taking flashloans (not an issue with Aave).
A similar issue might exist for manipulatable
totalValue
.
Impact
A temporarily artificially increased incomeIndex
would lead to an increased interestAmount in _payInterestToFunders
as the interestAmount
is multiplied by currentMoneyMarketIncomeIndex = moneyMarket.incomeIndex()
.
Similarly, a temporarily decreased incomeIndex
when funding leads to bigger payouts later as its divided by the intitial recordedMoneyMarketIncomeIndex
.
Recommended Mitigation Steps
Each protocol that is integrated must be checked that the incomeIndex
/totalValue
functions cannot be manipulated.
lack of zero address validation in constructor
Handle
Jmukesh
Vulnerability details
Impact
due to lack of zero address validation, there is chance of loosing funds
Proof of Concept
https://github.com/code-423n4/2021-05-88mph/blob/main/contracts/rewards/dumpers/Dumper.sol
https://github.com/code-423n4/2021-05-88mph/blob/main/contracts/rewards/dumpers/OneSplitDumper.sol
Tools Used
manual review
Recommended Mitigation Steps
add zero address check
Unchecking the ownership of `mph` in function `distributeFundingRewards` could cause several critical functions to revert
Handle
shw
Vulnerability details
Impact
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.
Proof of Concept
Referenced code:
MPHMinter.sol#L121
DInterest.sol#L1253
DInterest.sol#L1420
Tools Used
None
Recommended Mitigation Steps
Add a mph.owner() != address(this)
check as in the other functions (e.g., mintVested
).
Longer maturity leads to lower returns
Handle
cmichel
Vulnerability details
Vulnerability Details
The LinearDecayInterestModel
computes the interestAmount
on a deposit as depositAmount * moneyMarketInterestRatePerSecond * IRMultiplier * depositPeriodInSeconds
where the IRMultiplier
is a function of the depositPeriodInSeconds
computed in:
function getIRMultiplier(uint256 depositPeriodInSeconds)
public
view
returns (uint256)
{
uint256 multiplierDecrease = depositPeriodInSeconds * multiplierSlope;
if (multiplierDecrease >= multiplierIntercept) {
return 0;
} else {
return multiplierIntercept - multiplierDecrease;
}
}
The higher the duration the lower the interest amount, if the duration is long enough no interest will be returned.
Impact
I'd expect longer durations to lead to a higher percentage on the principal due to opportunity cost on the money.
If I take a loan with a long maturity (multiplierDecrease >= multiplierIntercept
) I get zero interest. Why would anyone do that?
Recommended Mitigation Steps
I might not understand what the intention is or the multiplier is wrong.
Gas optimizations - cache chainId
Handle
a_delamo
Vulnerability details
Impact
In Sponsorable
, the method _validateSponsorship
is calling all the time
uint256 chainId;
assembly {
chainId := chainid()
}
Being the chainId "static", we could keep the value cached and just refresh it from time to time?
Running on Remix the following code gives 134 gas cost.
function chain() external {
uint256 chainId;
assembly {
chainId := chainid()
}
}
Tools Used
None
contract AaveMarket function setRewards has a misleading revert message
Handle
paulius.eth
Vulnerability details
Impact
contract AaveMarket function setRewards has a misleading revert message:
require(newValue.isContract(), "HarvestMarket: not contract");
Recommended Mitigation Steps
Should be 'AaveMarket', not 'HarvestMarket'.
Anyone can withdraw vested amount on behalf of someone
Handle
cmichel
Vulnerability details
Vulnerability Details
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)
}
Impact
If the contract has no other functions to transfer out funds, they may be locked forever in this contract.
Recommended Mitigation Steps
Do not allow users to withdraw on behalf of other users.
Gas optimizations by using external over public
Handle
a_delamo
Vulnerability details
Impact
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;
}
surplusOfDeposit(uint256) should be declared external:
- DInterest.surplusOfDeposit(uint256) (contracts/DInterest.sol#623-648)
dividendOf(uint256,address,address) should be declared external:
- ERC1155DividendToken.dividendOf(uint256,address,address) (contracts/libs/ERC1155DividendToken.sol#101-107)
withdrawnDividendOf(uint256,address,address) should be declared external:
- ERC1155DividendToken.withdrawnDividendOf(uint256,address,address) (contracts/libs/ERC1155DividendToken.sol#114-126)
uri(uint256) should be declared external:
- ERC1155Upgradeable.uri(uint256) (contracts/libs/ERC1155Upgradeable.sol#92-94)
balanceOfBatch(address[],uint256[]) should be declared external:
- ERC1155Upgradeable.balanceOfBatch(address[],uint256[]) (contracts/libs/ERC1155Upgradeable.sol#124-143)
setApprovalForAll(address,bool) should be declared external:
- ERC1155Upgradeable.setApprovalForAll(address,bool) (contracts/libs/ERC1155Upgradeable.sol#148-160)
safeTransferFrom(address,address,uint256,uint256,bytes) should be declared external:
- ERC1155Upgradeable.safeTransferFrom(address,address,uint256,uint256,bytes) (contracts/libs/ERC1155Upgradeable.sol#178-190)
safeBatchTransferFrom(address,address,uint256[],uint256[],bytes) should be declared external:
- ERC1155Upgradeable.safeBatchTransferFrom(address,address,uint256[],uint256[],bytes) (contracts/libs/ERC1155Upgradeable.sol#195-207)
increaseAllowance(address,uint256) should be declared external:
- ERC20Wrapper.increaseAllowance(address,uint256) (contracts/libs/ERC20Wrapper.sol#146-157)
decreaseAllowance(address,uint256) should be declared external:
- ERC20Wrapper.decreaseAllowance(address,uint256) (contracts/libs/ERC20Wrapper.sol#173-186)
mint(address,uint256) should be declared external:
- ERC20Mock.mint(address,uint256) (contracts/mocks/ERC20Mock.sol#7-9)
deposit(uint256) should be declared external:
- VaultMock.deposit(uint256) (contracts/mocks/VaultMock.sol#16-21)
withdraw(uint256) should be declared external:
- VaultMock.withdraw(uint256) (contracts/mocks/VaultMock.sol#23-29)
deposit(uint256) should be declared external:
- VaultWithDepositFeeMock.deposit(uint256) (contracts/mocks/VaultWithDepositFeeMock.sol#23-33)
withdraw(uint256) should be declared external:
- VaultWithDepositFeeMock.withdraw(uint256) (contracts/mocks/VaultWithDepositFeeMock.sol#35-41)
updateAndQuery() should be declared external:
- EMAOracle.updateAndQuery() (contracts/models/interest-oracle/EMAOracle.sol#55-84)
query() should be declared external:
- EMAOracle.query() (contracts/models/interest-oracle/EMAOracle.sol#86-88)
initialize() should be declared external:
- MPHToken.initialize() (contracts/rewards/MPHToken.sol#19-23)
ownerMint(address,uint256) should be declared external:
- MPHToken.ownerMint(address,uint256) (contracts/rewards/MPHToken.sol#26-33)
Tools Used
Slither
Gas optimizations - storage over memory
Handle
a_delamo
Vulnerability details
Impact
Some functions are using memory to read state variables when using storage is more gas efficient.
function withdrawableAmountOfDeposit(
uint256 depositID,
uint256 virtualTokenAmount
) external view returns (uint256 withdrawableAmount, uint256 feeAmount) {
// Verify input
//FIXME: Storage
Deposit memory depositEntry = _getDeposit(depositID);
if (virtualTokenAmount == 0) {
return (0, 0);
} else {
if (virtualTokenAmount > depositEntry.virtualTokenTotalSupply) {
virtualTokenAmount = depositEntry.virtualTokenTotalSupply;
}
}
...
}
function _getVestWithdrawableAmount(uint256 vestID)
internal
view
returns (uint256 withdrawableAmount)
{
// read vest data
//FIXME: Storage
Vest memory vestEntry = _getVest(vestID);
DInterest pool = DInterest(vestEntry.pool);
DInterest.Deposit memory depositEntry =
pool.getDeposit(vestEntry.depositID);
...
}
The following functions contains memory than could be changed to storage:
- _computeAndUpdateFundingInterestAfterWithdraw
- _topupDepositRecordData
- _withdrawRecordData
- withdrawableAmountOfDeposit
- getInterestFeeAmount
- getEarlyWithdrawFeeAmount
- _getVestWithdrawableAmount
https://docs.soliditylang.org/en/v0.4.21/types.html#reference-types
Recommended Mitigation Steps
Test issue
Handle
adamavenir
Vulnerability details
Impact
Detailed description of the impact of this finding.
Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
Tools Used
Recommended Mitigation Steps
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.