Test a Smart Contract in Solidity with Constrained and Fuzzed Inputs

Noah Hein
InstructorNoah Hein

Share this video with your friends

Send Tweet
Published 7 months ago
Updated 5 months ago

Here in the same file, Contract.t.sol, we'll test your smart contract's increment and decrement functions. We will learn about how Forge will fuzz tests and how to properly constrain the vm object to ensure you are testing it under the right specific conditions.

Noah Hein: [0:01] The next thing that we'll want to do is we'll want to test the decrement. We can just copy a lot of this. We'll call this testDecrement. [0:16] Here, we will want to pass in a number since that's how our other decrement works. It takes an argument. We'll call this a number of type unsigned integer. We'll call this getNum. Then we will call the decrement, so we'll do number.decrement. Then we will call getNum right there. Here we can see if we do - num should be number.getNum.

[0:55] We would expect this to work. However, whenever we run forge test, you can see that this doesn't work. You can see that the reason is we're getting an unsafe decrement right here and that is not great.

[1:14] It does mean that our error is working, this unsafe decrement error is happening whenever we call a number. Why did that happen? You can see whenever Forge sees that you have put in a parameter for one of your functions, it will fuzz it for you.

[1:32] If you're not familiar with what fuzzing is, it just means that it is going to throw a bunch of different numbers into this function, and it's going to run it each time, and it's going to expect all of them to pass. You can see that it obviously did a number that was larger than 10 that would have caused some integer underflow, and so it reverted it but that means our test failed.

[1:56] In order to combat this, we have the ability to constrain the fuzzing numbers, and so we can call the VM -- is the virtual machine and it allows you to modify the runtime. We can say vm.assume, and we want to assume that our number is going to be smaller than whatever number.getNum is.

[2:24] Now, whenever we run this test, you'll see that all of the numbers will be small enough, and we won't run into that error again. Here, if we run forge test, let's go ahead and make that bigger. You could see, cool, we are getting two OKs.

[2:41] It's good to be able to test that if we made any breaking changes. We want to have a negative test as well to make sure that "Hey, this is still failing whenever this vm.assume isn't there." What we'll do is we'll turn this comparison sign. We will flip it on its head. We want to say, "Hey, the number is always going to be larger than whatever our current value is."

[3:10] If we do this, we should see that our test suite fails. Awesome. You can see our testDecrement is bad. It also is complaining that we have the same thing twice. You would see that it fails for the same reason that the other one did. Aside from the different names, we want to do this testFail. If you add this Fail right here, Forge will expect the assertion to fail.

[3:38] Here, if we run this and we do forge test, you can see that all of our things are passing. With that, we don't need to assert equality here because we are never getting to this line. This number.decrement is always going to be throwing an error. We can remove that. See, the forge test is still running. All of them are passing.