GithubHelp home page GithubHelp logo

mantle-token-contracts's People

Contributors

afkbyte avatar agnarsong avatar aodhgan avatar franck44 avatar shellteo avatar tri-stone avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mantle-token-contracts's Issues

What is the purpose of `deposit`?

The deposit function reads as follows:

function deposit(uint256 _amount) external {
        require(address(mantle) != address(0), "Zero address: mantle");
        IERC20(mantle).safeTransferFrom(msg.sender, address(this), _amount);

        emit Deposit(msg.sender, _amount);
}

In essence its body allows to transfer Mantle tokens (from ERC-20 mantle contract) from a given account (msg.sender) to the token migration contract (this).
As a result it acts as a proxy to the mantle contract, specialised to transfer to the token migration contract.

It is not clear why we need this function:

  • its sole action is an external call to the mantle contract (arguably there is another effect which is a Deposit event appended to the log)
  • the functionality that it provides is already available directly via the mantle contract
  • using an indirection and external call to achieve the transfer results in far more expensive Tx (external calls are expensive)
  • the mantle contract (address) can only be set once, and we need to have set this address before deposit can be called, so anyway we should have deployed the ERC-20 Mantle contract and thus have its address
  • the function name deposit is not very informative about what is happening, you need to know the implementation to realise that it transfers mantle tokens (and not other tokens).

Add specification and documentation

Objective

Add comprehensive documentation to the contract

Tasks

Document the contract, variables, functions and provide clear

  • intent (what the function should be doing)
  • parameters
  • result(s)
  • requirements

[QS][MNT-01] Adherence To Specification: BIT Holders Might Get Less MNT (High)

Description
The proposal in BitDAO states: “The one-way token conversion ratio shall be set at 1 $BIT token to 1 $MNT token”. However, the MantleTokenMigrator contract computes the ratio based on TOKEN_CONVERSION_NUMERATOR and TOKEN_CONVERSION_DENOMINATOR , possibly not guaranteeing the 1:1 ratio.

Recommendation:
Remove such constants and conversion calculations or updating the proposal and other related documentation.

[QS][MNT-03] Missing Input Validation (Low)

Description
It is important to validate inputs, even if they only come from trusted addresses, to avoid human error. The following is a non-exhaustive list of missing input validations:

  1. MantleTokenMigrator.constructor() : the TOKEN_CONVERSION_NUMERATOR has to be less than or equal to TOKEN_CONVERSION_DENOMINATOR . Additionally, the decimals of BIT and MNT tokens should be equal according to the NatSpeccomments.

  2. MantleTokenMigrator.setTreasure() needs a check against address(0) . Otherwise, the defundContract() , might just burntokens.

Recommendation:
Adding the relevant checks

[OZ][N-05] Redundant cast (Informational)

Description:
The sweepTokens function of the MantleTokenMigrator contract redundantly casts both
known token addresses to the address type.

Recommendation:
Consider removing the unnecessary cast operations.

[OZ][L-01] Misleading comments (Low)

Description:
We identified the following misleading comments:

  • The comment describing the setMintCapNumerator references a MintCapNumeratorSet event, but it should be MintCapNumeratorChanged .
  • The comment describing the mint function says the mint time interval "is initially set to
    1 year", which suggests it could be updated. It's actually a constant and can only be
    changed if the whole contract is upgraded.

** Recommendation:**
Consider updating the comments accordingly.

[OZ][N-02] Ether handling can be simplified (Informational)

Description:
The MantleTokenMigrator contract doesn't expect to receive Ether and explicitly reverts
on the receive and fallback functions. As noted in the Solidity documentation, without
the receive , fallback , and payable functions a Solidity contract reverts on receiving
Ether by default.

Recommendation:
Consider removing the receive and fallback functions to simplify the
codebase.

[OZ][N-04] Imprecise docstrings (Informational)

Description:
We have identified some imprecise docstrings:

  • the comment describing the parameter for the setMintCapNumerator function does not follow the Ethereum Natural Specification Format (NatSpec) format.

  • for consistency with the migrateAllBIT description, the migrateBIT description should note that the _amount must be non-zero.

Recommendation:
Consider updating the docstrings accordingly

[QS][MNT-04] Unnecessary receive() and fallback() functionality (Informational)

Description:
The MantleTokenMigrator contract implements receive() and fallback() functions. Based on the documentation and project use cases, the contract does not need to receive ETH at any point. The implementation of the functions, also show the Mantle team intention: to make the transaction revert when receiving ETH in this contract.

Recommendation:
Remove receive() and fallback() functions from MantleTokenMigrator as the contract is not expected to receive ETH. This will make the contract more clear.

Strategy for setting the address of the BIT and MNT ERC-20 contracts

Current Design

In the current design of the contract, there is a function to set the address of the new Mantle token ERC-20 contract:

function setMantle(address _mantle) external onlyOwner {
        require(address(mantle) == address(0), "Already set, only can be set once");
        require(_mantle != address(0), "Zero address: mantle");

        mantle = IERC20(_mantle);
}

Advantages for having this function

  • it enables us to set (but not to reset) the base MNT token contract address
  • this is useful if we want to test on an test network before we deploy on mainNet

Disadvantages

  • the requirements imply that we can only set the variable once. In that case it could be immutable.
  • there is no guarantee that we initialise with an actual ERC-20 contract (_mantle just has to be non zero and then we cast the address to IERC20), and not even with our Mantle ERC-20 token contract.
  • it adds a function in the code that can be used only once (similar to a constructor).

Possible Other Design

(s1) We can hard-code the value of the MNT ERC-20 contract. This has the following advantages:

  • no need for a setMantle function (that can only be used once)
  • guarantee that we have an address that is an ERC-20 contract in mantle (if we cut/paste the address correctly)

The same applies to bit.

(s2) We could also set the value of mantle in the constructor as for bit.

What about testing?

The strategy (s1) above can be implemented in testing by providing the addresses of the contracts on the testnet.
The contract that is to be deployed on mainNet will have different values for this constant variables.

Other consideration

Are there any examples of similar contracts (in other similar projects) where the address of a fixed contract is set via proxy (a function) that can only be used once?
I have seen several values of fixed addresses hard-coded in contracts.

It is not clear what is best but I could not find a lot on this topic except:
https://stackoverflow.com/questions/69050570/how-to-avoid-hardcoding-contract-address-in-solidity

[QS][MNT-05] Ownership Can Be Renounced (Low)

Description:
If the owner renounces their ownership, or the ownership is transferred to a wrong address, all ownable contracts will be left without an owner.
During the audit, the auditing team found the following issues:

  • L1MantleToken:
    • OwnableUpgradeable from OpenZeppelin implements renounceOwnership() function. If the contract owner calls this function, all the functions guarded by onlyOwner will no longer be able to be executed.
    • OwnableUpgradeable implements transferOwnership(). If the new owner address is wrong or not controlled by the team, the access to priviledged functions (onlyOwner) will be lost forever.
  • MantleTokenMigrator:
    • This contract implements a custom function for transferring the contract ownership: transferOwnership(). If the new owner address is wrong or not controlled by the team, the access to priviledged functions (onlyOwner) will be lost forever.

Recommendation:

  • L1MantleToken:
    • Confirm that this is the intended behavior. If not, override and disable the renounceOwnership() function.
    • Consider using a two-step process when transferring the ownership of the contract (e.g. Ownable2StepUpgradeable from OpenZeppelin).
  • MantleTokenMigrator:
    • Consider using a two-step process when transferring the ownership of the contract (e.g. Ownable2StepUpgradeable from OpenZeppelin).

[OZ][N-06] Unnecessary multiple inheritance (Informational)

Description:
The L1MantleToken contract inherits from several contracts with redundant dependencies. This means that some of the contracts are inherited both directly and indirectly. For example, inheriting ERC20VotesUpgradeable makes inheriting ERC20PermitUpgradeable and ERC20Upgradeable redundant.

This is still a reasonable pattern because it improves explicitness and makes the sequence of initializations more intuitive. However, it also forces the L1MantleToken to introduce boilerplate functions that are unrelated to the token's new logic. Our opinion is that removing redundancy from the inheritance chain would make the contract simpler and easier to reason about.

Recommendation:
Consider limiting the inheritance chain to the necessary contracts (ie. ERC20BurnableUpgradeable , OwnableUpgradeable and ERC20VotesUpgradeable ).

[OZ][N-03] Gas savings (Informational)

Description:
The setMintCapNumerator and setTreasury functions can consume less gas by emitting an event first and then changing the storage variable.

For example, the following code snippet

uint256 previousMintCapNumerator = mintCapNumerator;
mintCapNumerator = _mintCapNumerator;
emit MintCapNumeratorChanged(msg.sender, previousMintCapNumerator, mintCapNumerator);

May be rewritten as

emit MintCapNumeratorChanged(msg.sender, mintCapNumerator, _mintCapNumerator);
mintCapNumerator = _mintCapNumerator;

Recommendation:
Consider rewriting the setMintCapNumerator and setTreasury functions for gas savings.

Review visibility of variables and functions

Check that the visibility of variables is adequate.
For instance, enabled may be internal and we don't need a getter (generated by the compiler for public variables) for it.

Review messages in `requires`

Some messages in the requires are not very explicit or misleading.

e.g. line 61: require(_bit != address(0), "Zero address: bit") -> "Initialisation of ERC-20 BIT contract cannot be zero"
e.g. line 83: require(enabled, "Migration: migrate enabled"); -> "Migration disabled" or "Migration not enabled"

[QS][MNT-02] Defund Protection Bypassed (Medium)

Description
defundContract() can be used by the owner of the contract to transfer the funds from the migrator contract to the treasury address. However, the owner can just change the treasury variable with the same privileges and therefore transfer the funds to an arbitrary address.

Recommendation:
Confirm this is the expected behavior. If not, defundContract() and setTreasury() should be protected by different roles or keys (a multi-signature wallet approach would mitigate this as well). In this case, a malicious owner will only be able to transfer the funds to a fixed treasury address by another guardian or administrator.

[QS][MNT-06] Old Solidity Version (Low)

Description:
The codebase is compiled using the 0.8.13 version of Solidity. Several versions have been released after that, fixing bugs and improving optimization.

Recommendation:
Using a newer compiler version

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.