A beginner’s guide to MythX

By Mike Pumphrey | Tuesday, November 26th, 2019

A detailed, step-by-step howto guide on how to use MythX with Remix, showing as well as the differences between MythX and MythX Pro.

MythX is a tool for finding smart contract weaknesses. For our single developers and dev teams, we offer two plans: MythX and MythX Pro.

(We also offer custom plans too; contact us for details.)

We recently posted about the differences between MythX and MythX Pro. But you may find it more useful to see an actual scenario involving testing a smart contract using MythX.

Let’s meet Sam.

Contract and scenario

Sam is a smart contract developer, who is working on a project that will pay out multiple people at once based on balances stored in a contract.

After some research and testing, Sam has come up with this:

pragma solidity 0.5.11;

contract Demo {
    address payable owner;
    address payable[] receivers;
    mapping (address => uint256) balance;

    constructor() public {
        owner = msg.sender;
    }

    function setReceivers(address payable[] memory _receivers) public {
        receivers = _receivers;
    }

    function deposit() public payable {
        balance[msg.sender] += msg.value;
        receivers.push(msg.sender);
    }

    function payReceivers() public {
        for(uint256 i = 0; i < receivers.length; i++) {
            (bool success,) = receivers[i].call.value(balance[receivers[i]])("");
            require(success);
        }
    }

    function die(address caller) public {
        require(caller == msg.sender);
        selfdestruct(msg.sender);
    }
}

As a work in progress, Sam isn't quite ready for a manual audit yet, but wants to check the contract for weaknesses now. This way, one can fix the most obvious problems before getting to the audit.

Running MythX

Sam's tool of choice is Remix, so this is what we'll be using here. (You can follow along too, though you can use any tool of your choice. The results will be the same.)

1. Open Remix.

2. Open the contract file in the editor.

3. Use the Solidity compiler to compile your contract.

4. Enable the MythX plugin by going to the Plugin Manager, finding MythX Security Verification, and click Activate.

(If you see a “permission needed” window, click Accept.)

5. Enter your MythX credentials and click "Save". (You can sign up for a free account at mythx.io.)

6. Click "Analyze".

By default, MythX will analyze your contract using the standard "quick" mode of a 2 minute scan. When finished, the results will be displayed in the main window.

[30:8] The contract can be killed by anyone. [SWC-106] 
[4:20] The state variable visibility is not set. [SWC-108]
[5:22] The state variable visibility is not set. [SWC-108]
[6:33] The state variable visibility is not set. [SWC-108]

Viewing results

Let's take a look at each of the results in greater detail.

[4:20] The state variable visibility is not set. [SWC-108]

Variable visibility is crucial in smart contracts. Not specifying variable visibility can lead to confusion for coders, auditors, and users of your contract. A public variable will be easily accessible to users of your contract, and it will be unhelpful if the variable is public but doesn’t need to be.

The best practice here is to use the most restrictive visibility level possible.

The default visibility for a variable is public, which is what we see with the owner variable, which in this case we don’t want. You would want to change the line:

address payable owner

To:

address payable private owner

The same thing is true for the other two warnings:

[5:22] The state variable visibility is not set. [SWC-108]
[6:33] The state variable visibility is not set. [SWC-108]

These are referring to the receiver’s address and the balance mapping, both of which you would want to declare as public, as they are meant to be in use by users of the contract.

The above weaknesses certainly should be fixed, but the next one is far worse:

[30:8] The contract can be killed by anyone. [SWC-106]

This weakness refers to the EVM SELFDESTRUCT functionality available on line 30. Obviously, we don't want just anyone to be able to kill the contract, and we may not want the contract to be killed by anyone at all.

(For amateur historians, this was the weakness that was involved in the Parity wallet issue back in 2017, which you may know by the infamous "I accidentally killed it" quote.)

The way the die() method is implemented, it does not actually check that the caller is the owner.

It should be rewritten to actually check the owner:

function die() public {
    require(owner == msg.sender);
    selfdestruct(msg.sender);
}

Using MythX Pro

Notice the warning below the weaknesses:

Upgrade to MythX Pro to unlock the ability to test for even more vulnerabilities, perform deeper security analysis, and more.

MythX Pro detects more than a dozen additional issues that the free version of MythX doesn't catch. Important weaknesses such as Reentrancy, Timestamp Dependence, and Weak Randomness.

Sam realizes this, and decides to purchase a MythX Pro subscription, using DAI associated with the registered Ethereum address on the account.

Running the analysis in Remix again on the original contract with a MythX Pro subscription, one can utilize "full” mode, which runs for up to 15 times longer than “quick” mode. This is a good thing, as the longer the built-in MythX fuzzer runs, the more weaknesses you could potentially find.

Viewing results (part 2)

The results come in and the following additional vulnerabilities were found:

[23:30]  error    persistent state read after call [SWC-107]
[23:30]  error    persistent state write after call [SWC-107]
[22:31]  error    persistent state read after call [SWC-107]
[23:62]  error    persistent state read after call [SWC-107]
[23:54]  error    persistent state read after call [SWC-107]
[23:30]  warning  multiple external calls [SWC-113]
[4:4]    warning  Unused state variable "owner" [SWC-131] 
[3:0]    warning  Potential denial-of-service if block gas limit is reached [SWC-128] 

Yikes!

We've found quite a few more issues. Let's take them one by one:

[3:0]    warning  Potential denial-of-service if block gas limit is reached [SWC-128]  

SWC-128 refers to the potential for arrays to grow larger over time. If the array grows large enough, the gas required could exceed the block gas limit.

And indeed our contract does have arrays which can grow unbounded. The setReceivers method allows for the receivers array to be set to an arbitrary size, which is unnecessary. The entire setReceivers method can either be deleted or can be restricted to the contract owner, depending on the developer’s goal.

In addition, the deposit() method, when called multiple times, will constantly increase the size of receivers. This is more difficult to fix. The best fix for denial of service issues in loops is to change how the contract pays out Ether. Instead of using pushing Ether to all the users at once, each user should be able to pull their own Ether from the contract. 

 [4:4]    warning  Unused state variable "owner" [SWC-131]  

In our contract, owner doesn't really do anything. It's set to payable, and then set to be equal to msg.sender. This actually hints at a deeper logic vulnerability, which MythX reported in this case. The developer of this contract intended for the die() method to be restricted to the owner, but since owner is never used, we know the check is not being performed correctly. As mentioned previously, the die() method needed to be rewritten to correctly check that msg.sender is equal to owner. 

[23:30]  error    persistent state read after call [SWC-107]
[23:30]  error    persistent state write after call [SWC-107]
[22:31]  error    persistent state read after call [SWC-107]
[23:62]  error    persistent state read after call [SWC-107]
[23:54]  error    persistent state read after call [SWC-107] 

Wow, that particular For loop has some big problems! SWC-107 refers to "reentrancy", in which a malicious contract can call back into the calling contract before the first call is completed. This can cause some serious and undesirable actions. In this contract, it can result in a malicious party stealing all the Ether in it.

The trick here is to ensure that all internal state changes are performed before any call is executed, not after. And here we see five places where that hasn't happened.

The easiest way to fix this would be to stop using the For loop to push Ether at all. Having users pull their Ether is much more secure than pushing it to everyone at once.

Finally, we have a warning:

 [23:30]  warning  multiple external calls [SWC-113]

This For loop changes the balance of every entry in the array. But each one of those balance changes is an external call, and external calls can lead to denial of service issues.

This is also fixed by ditching the For loop altogether, and switching to letting users pull their Ether from the contract.

Fixed contract

And with that, we have the following fixed contract:

pragma solidity 0.5.11;

contract Demo {
    address payable private owner;
    address payable[] public receivers;
    mapping (address => uint256) public balance;

    constructor() public {
    	owner = msg.sender;
    }

    function deposit() public payable {
    	balance[msg.sender] += msg.value;
    	receivers.push(msg.sender);
    }

    function payreceiver(address receiver) public {
    	require(balance[receiver] >= 0);
    	uint256 amount = balance[receiver];
    	balance[receiver] = 0;
    	(bool success,) = receiver.call.value(amount)("");
    	require(success);
    }

    function die() public {
    	require(owner == msg.sender);
    	selfdestruct(msg.sender);
    }
}

MythX Pro will then only return one informational issue which can be safely ignored.

If Sam were done with development at this point, then she would be ready to do a manual audit, to check that her business logic is correct. After all, MythX doesn't replace an audit, it prepares you for one.

Conclusion

Here we've given you look at the differences between MythX and MythX Pro through the usage of a simple smart contract analysis.

It's easy to get started with MythX; just create an account and use a tool of your choice. But if you want the best in protection against security weaknesses, MythX Pro is what you want. Sam would agree.

Special thanks to Nathan Peercy, who created the contracts for this article, and provided much of the commentary on the security issues.

Want to learn more? Sign up for the MythX Newsletter and get how-tos like this sent right to your inbox.

Mike Pumphrey

Mike Pumphrey is Marketing and Brand Manager at MythX, a division of ConsenSys. With over a dozen years of experience in training, education, and promotion, Mike is passionate about making technical concepts understandable to a wider audience. He believes that great software can only be great when people know how to use it, and is excited to be bringing clarity to a field as exciting and new as blockchain.
Mike Pumphrey

Latest posts by Mike Pumphrey (see all)