MorpheusAI

MorpheusAI
Foundry
22,500 USDC
View results
Submission Details
Severity: medium
Invalid

Non-Standard ERC20 Tokens should not be supported

Summary

As the Distribution_init function in Distribution.sol implementation permits any token without undergoing a legitimacy check, it is possible to introduce a valid token that is not ERC20 compliant, (or an invalid token which can be non compliant as well).

Vulnerability Details

According to Morpheus documentation (https://github.com/MorpheusAIs/Morpheus/blob/main/!KEYDOCS%20README%20FIRST!/2.WhitePaper.md) the expected token for interacting with the contracts is stETH. For example:

“The Morpheus full node comes with a wallet or the user can connect their existing wallet. This enables the user to sign and send transactions recommended by their Smart Agent. So users will be able to participate in the proofs through the Morpheus software. However Capital Providers are not required to have a full node for example. They can interact directly with the Smart Contracts on Ethereum / Arbitrum using stETH”.

The token is expected to be ERC20 compliant, i.e. tokens that must adhere to guidelines outlined by ERC20 to be considered ERC20-compliant.
The functions a token must have are:

  • TotalSupply: The total number of tokens that will ever be issued

  • BalanceOf: The account balance of a token owner's account

  • Transfer: Automatically executes transfers of a specified number of tokens to a specified address for transactions using the token

  • TransferFrom: Automatically executes transfers of a specified number of tokens from a specified address using the token

  • Approve: Allows a spender to withdraw a set number of tokens from a specified account, up to a specific amount

  • Allowance: Returns a set number of tokens from a spender to the owner

The events that must be included in the token are:

  • Transfer: An event triggered when a transfer is successful

  • Approval: A log of an approved event (an event)

Distribution_init function allows to set incorrect data, with invalid pools according to the tests provided below. As mentioned in the documentation this function is only meant to be called with a valid ERC20 token (the stETH) but the function is generic enough to accept non compliant tokens.

Proof of Concept

As a proof of concept a fake token contract was included into the /tokens folder named FooToken.sol

At a glance, this fake token is not ERC20 compliant, and it is still accepted into the Pool creation:

pragma solidity >=0.7.0 <0.9.0;
import "hardhat/console.sol";
contract FooToken {
string public constant name = "FooToken";
string public constant symbol = "FOO";
uint8 public constant decimals = 18;
uint256 public totalPooledEther;
uint256 public totalShares;
uint256 totalSupply_;
mapping(address => uint256) balances;
mapping(address => mapping(address => uint256)) allowed;
mapping(address => uint256) private shares;
constructor() {
totalSupply_ = 1000000 * 10 ** decimals;
balances[msg.sender] = totalSupply_;
totalPooledEther = 10 ** 10;
console.log("Injecting Fake Token:", name );
}
function totalSupply() public view returns (uint256) {
return totalSupply_;
}
function mint(address _account, uint256 _amount) external {
uint256 sharesAmount = getSharesByPooledEth(_amount);
_mintShares(_account, sharesAmount);
totalPooledEther += _amount;
}
function _mintShares(address _recipient, uint256 _sharesAmount) internal returns (uint256 newTotalShares) {
require(_recipient != address(0), "MINT_TO_ZERO_ADDR");
totalShares += _sharesAmount;
shares[_recipient] += _sharesAmount;
return totalShares;
}
function getSharesByPooledEth(uint256 _ethAmount) public view returns (uint256) {
return (_ethAmount * totalShares) / totalPooledEther;
}
function balanceOf(address tokenOwner) public view returns (uint) {
return balances[tokenOwner];
}
function approve(address delegate, uint numTokens) public returns (bool) {
allowed[msg.sender][delegate] = numTokens;
return true;
}
}

This fake token can be used in the depositedToken variable and will be included during the Pool creation:

let rewardToken: MOR;
let depositToken: FooToken;

When running the test and the FooToken is injected, we verify that our Fake, non compliant token is accepted into the Distribution_init function when this should be prohibited:

Distribution
Injecting Fake Token: FooToken
UUPS proxy functionality
#constructor
✔ should disable initialize function (40ms)
#Distribution_init
✔ should set correct data after creation
✔ should create pools with correct data (113ms)
✔ should revert if try to call init function twice
#_authorizeUpgrade
✔ should correctly upgrade (46ms)
✔ should revert if caller is not the owner
✔ should revert if `isNotUpgradeable == true`
#createPool
✔ should create pool with correct data (41ms)
✔ should correctly pool with constant reward
✔ should revert if caller is not owner
should revert if try to create pool with incorrect data
if `payoutStart == 0`
if `decreaseInterval == 0`
#editPool
✔ should edit pool with correct data
✔ should revert if try to change pool type
✔ should revert if caller is not owner
✔ should revert if pool doesn't exist
should revert if try to edit pool with incorrect data
if `decreaseInterval == 0`

Impact

Verifying the ERC20 compliance of the token is essential for validating pool creation. Neglecting this verification could result in the formation of invalid pools, triggering unforeseen issues in various functionalities of the Distribution.sol contract, including the potential for a Denial of Service (DoS) scenario.

Recommendations

Properly managing non-standard ERC20 contracts is essential for their support. A recommended approach is to employ a trusted library like OpenZeppelin’s SafeERC20.

References

https://solodit.xyz/issues/make-token-erc20-compliant-openzeppelin-unikoingold-token-audit-markdown
https://solodit.xyz/issues/non-standard-erc20-tokens-are-not-supported-and-locked-in-the-contract-sigmaprime-none-sushi-pdf

Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
mrjorystewartbaxter Submitter
over 1 year ago
inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.