1

my question is simple. I'm Auditing the Ethernaut22 Contract: here for code https://ethernaut.openzeppelin.com/level/0x9CB391dbcD447E645D6Cb55dE6ca23164130D008

Ethernaut22.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";


contract Ethernaut22 is Ownable{

    address public token1;
    address public token2;


    constructor() {}



    function setTokens(address _token1, address _token2) public onlyOwner {
        token1 = _token1;
        token2 = _token2;
    }



    function addLiquidity(address token_address, uint amount) public onlyOwner {
        IERC20(token_address).transferFrom(msg.sender, address(this), amount);
    }



    function swap(address from, address to, uint amount) public {
        require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
        require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
        uint swapAmount = getSwapPrice(from, to, amount);
        IERC20(from).transferFrom(msg.sender, address(this), amount);
        IERC20(to).approve(address(this), swapAmount);
        IERC20(to).transferFrom(address(this), msg.sender, swapAmount);
    }



    function getSwapPrice(address from, address to, uint amount) public view returns(uint){
        return((amount * IERC20(to).balanceOf(address(this)))/IERC20(from).balanceOf(address(this)));
    }



    function approve(address spender, uint amount) public {
        Ethernaut22Token(token1).approve(msg.sender, spender, amount);
        Ethernaut22Token(token2).approve(msg.sender, spender, amount);
    }



    function balanceOf(address token, address account) public view returns (uint){
        return IERC20(token).balanceOf(account);
    }
}



contract Ethernaut22Token is ERC20 {
    address private _dex;


    constructor(
        address dexInstance,
        string memory name,
        string memory symbol,
        uint256 initialSupply
    )ERC20(name, symbol){
        _mint(msg.sender, initialSupply);
        _dex = dexInstance;
    }


    function approve(address owner, address spender, uint256 amount) public {
        require(owner != _dex, "InvalidApprover");
        super._approve(owner, spender, amount);
    }
}

There are many errors in this code, but i would like to understand something from my echidna fuzzing

This is the code:

//SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

import "../Ethernaut22.sol";
import "@crytic/properties/contracts/util/Hevm.sol";

//Fuzzing Ethernaut22 Contract
//

contract fuzzerEthernaut22 is Ethernaut22{


    address constant deployer = address(0x20000);
    address constant dist = address(0x30000);
    address token1Address;
    address token2Address;


    constructor(){
        token1Address = address(new Ethernaut22Token(address(this), "Token1", "TKN1", 110));
        token2Address = address(new Ethernaut22Token(address(this), "Token2", "TKN2", 110));
        Ethernaut22Token(token1Address).transfer(deployer, 110);
        Ethernaut22Token(token2Address).transfer(deployer, 110);
    }



    function checkOwner()public{
        address actualOwner = owner();
        assert(owner() == deployer);
    }




    function checkBalance()public{
        uint amount1 = Ethernaut22Token(token1Address).balanceOf(deployer);
        assert(amount1 == 110);
    }



    function check2Balance()public{
        uint amount2 = Ethernaut22Token(token2Address).balanceOf(deployer);
        assert(amount2 == 110);
    }

    ----ERROR HERE-----
    //False-Negative?
    function check_access_from_another_address_on_set_tokens_function()public{
        hevm.prank(dist);
        setTokens(token1Address, token2Address);
        assert(token1 == address(0));
        assert(token2 == address(0));
    }
}

and this is also my config.yaml file

testMode: assertion
testLimit: 50000
deployer: "0x20000"
sender: ["0x20000" ,"0x30000"]
psender: "0x20000"

cryticArgs: ['--solc-remaps', '@=node_modules/@']


filterFunctions: ["fuzzerEthernaut22.transferOwnership(address)", "fuzzerEthernaut22.renounceOwnership()"]
filterBlacklist: true

If i run Echidna via my config.yaml i got an error in function check_access_from_another_address_on_set_tokens_function()public{}

Scope of the property is to check the possibility to set tokens if sender it's not the owner contract.

If token 1 and token 2 are equals to 0, this means, dist, it's not been able to set tokens because is not the owner.

The error i got is:

approve(address,uint256):  passed! 
token2():  passed! 
addLiquidity(address,uint256):  passed! 
check_access_from_another_address_on_set_tokens_function(): failed!
  Call sequence:
    check_access_from_another_address_on_set_tokens_function()

Event sequence: Panic(1): Using assert.
owner():  passed! 
check2Balance():  passed! 
getSwapPrice(address,address,uint256):  passed! 
checkBalance():  passed! 
setTokens(address,address):  passed! 
checkOwner():  passed! 
token1():  passed! 
swap(address,address,uint256):  passed! 
balanceOf(address,address):  passed! 
AssertionFailed(..):  passed! 
Unique instructions: 3129
Unique codehashes: 2
Corpus size: 18
Seed: 1397477069376912808

It's looks like, i can't recognize my hevm.prank(dist) to call function from another address, so the sender is still the deployer.

At the same time, following the echidna documentation, should be typical case of false negative coming from the use of Hevm.

Can someone explain me why i got this? Thanks in advice, have a nice day

1 Answers1

1

Maybe i just found a workaround to my issue.

//SPDX-License-Identifier: AGPL-3.0

pragma solidity ^0.8.0;

import "../Ethernaut22.sol";

//Fuzzing Ethernaut22 Contract
//


contract fuzzerEthernaut22 is Ethernaut22{

    address constant deployer = address(0x20000);
    address constant dist = address(0x10000);

    address public token1Address;
    address public token2Address;

    event AssertionFailed(string);

    constructor(){
        token1Address = address(new Ethernaut22Token(address(this), "Token1", "TKN1", 110));
        token2Address = address(new Ethernaut22Token(address(this), "Token2", "TKN2", 110));
        Ethernaut22Token(token1Address).transfer(deployer, 110);
        Ethernaut22Token(token2Address).transfer(deployer, 110);
    }


    // Emitting an AssertionFailed because in config.yaml we setted 0x10000 as sender
    // Passed
    function check_access_from_another_address_on_set_tokens_function()public{
        if(msg.sender == owner()){
            setTokens(token1Address, token2Address);
            assert(token1 != address(0));
            assert(token2 != address(0));
        }else{
            emit AssertionFailed("Sender is not the contract owner!");
        }
    }
}

my config.yaml

testMode: assertion
testLimit: 50000

deployer: "0x20000"
sender: ["0x10000"]


cryticArgs: ['--solc-remaps', '@=node_modules/@']


filterFunctions: ["fuzzerEthernaut22.transferOwnership(address)", "fuzzerEthernaut22.renounceOwnership()"]
filterBlacklist: true

I just avoid using of hevm because it's like i can't recognized that. In config.yaml i just setted sender as address 0x10000 and deployer as 0x20000

So using Ownable.sol and setted owner() as deployer(0x20000) if i try to call function setTokens(address, address)public onlyOwner{...} via different address i just receive a revert.

this is the result calling echidna via config.yaml and sender setted on 0x10000

fuzzerEthernaut22
approve(address,uint256):  passed! 
token2Address():  passed!                                                                                        
token2():  passed!                                                                                               
check_balance_deployer_token1():  passed!                                                                        
check_owner():  passed!                                                                                          
check_addresses():  passed!                                                                                      
check_balance_deployer_token2():  passed!                                                                        
addLiquidity(address,uint256):  passed!                                                                          
check_access_from_another_address_on_set_tokens_function(): failed!                                              
  Call sequence:                                                                                                   
    check_access_from_another_address_on_set_tokens_function()                                                     
                                                                                                                   
Event sequence: AssertionFailed(«Sender is not the contract owner!») from: 0xa329c0648769a73afac7f9381e08fb43dbea72
owner():  passed!                                                                                                
getSwapPrice(address,address,uint256):  passed!                                                                  
setTokens(address,address):  passed! 
token1():  passed! 
token1Address():  passed! 
swap(address,address,uint256):  passed! 
balanceOf(address,address):  passed! 
AssertionFailed(..): failed!
  Call sequence:
    check_access_from_another_address_on_set_tokens_function()

Event sequence: AssertionFailed(«Sender is not the contract owner!») from: 0xa329c0648769a73afac7f9381e08fb43dbea72
Unique instructions: 1912
Unique codehashes: 2
Corpus size: 12
Seed: 5517650708752309081

this is the result calling echidna via config.yaml and sender setted on 0x20000

fuzzerEthernaut22
approve(address,uint256):  passed! 
token2Address():  passed!                                                                                                        
token2():  passed!                                                                                                               
addLiquidity(address,uint256):  passed! 
check_access_from_another_address_on_set_tokens_function():  passed! 
owner():  passed! 
getSwapPrice(address,address,uint256):  passed! 
setTokens(address,address):  passed! 
token1():  passed! 
token1Address():  passed! 
swap(address,address,uint256):  passed! 
balanceOf(address,address):  passed! 
AssertionFailed(..):  passed! 
Unique instructions: 3469
Unique codehashes: 2
Corpus size: 19
Seed: 4335292047975925473

Hoping this workaround should be usefull to someone.

Please let me know if there is something wrong(specially for Hevm using)