Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

calling ds-test/test.sol's failed() results in 'NoneType' object has no attribute 'size' #358

Closed
karmacoma-eth opened this issue Aug 27, 2024 · 6 comments · Fixed by #359
Labels
bug Something isn't working

Comments

@karmacoma-eth
Copy link
Collaborator

Describe the bug

with halmos 0.1.14.dev15+g060a6d8.d20240813:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import "forge-std/Test.sol";
import {console2} from "forge-std/console2.sol";

contract Test91 is Test {
    function test_dstestFailed() external {
        console2.log("failed() =", failed());
    }
}
halmos --function test_dstestFailed --debug

Executing test_dstestFailed
Address 0x7109709ecfa91a80626ff3989d68f67f5b1dd12d not in: [0x7fa9385be102ac3eac297483dd6233d62b3e1496]
Empty address: 0x7109709ecfa91a80626ff3989d68f67f5b1dd12d
Address 0x7109709ecfa91a80626ff3989d68f67f5b1dd12d not in: [0x7fa9385be102ac3eac297483dd6233d62b3e1496]
Empty address: 0x7109709ecfa91a80626ff3989d68f67f5b1dd12d
Address 0x7109709ecfa91a80626ff3989d68f67f5b1dd12d not in: [0x7fa9385be102ac3eac297483dd6233d62b3e1496]
Empty address: 0x7109709ecfa91a80626ff3989d68f67f5b1dd12d
[ERROR] test_dstestFailed()
AttributeError: 'NoneType' object has no attribute 'size'
Traceback (most recent call last):
  File "/Users/karma/projects/halmos/src/halmos/__main__.py", line 1003, in run_sequential
    test_result = run(setup_ex, run_args.abi, fun_info, test_config)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/karma/projects/halmos/src/halmos/__main__.py", line 727, in run
    for idx, ex in enumerate(exs):
  File "/Users/karma/projects/halmos/src/halmos/sevm.py", line 2651, in run
    self.call(ex, opcode, to_alias, stack, step_id)
  File "/Users/karma/projects/halmos/src/halmos/sevm.py", line 2064, in call
    call_unknown()
  File "/Users/karma/projects/halmos/src/halmos/sevm.py", line 1995, in call_unknown
    ret = hevm_cheat_code.handle(self, ex, arg, stack, step_id)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/karma/projects/halmos/src/halmos/cheatcodes.py", line 542, in handle
    return ByteVec(sevm.sload(ex, load_account_alias, load_slot))
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/karma/projects/halmos/src/halmos/sevm.py", line 1679, in sload
    return self.storage_model.load(ex, addr, loc)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/karma/projects/halmos/src/halmos/sevm.py", line 1297, in load
    cls.init(ex, addr, loc)
  File "/Users/karma/projects/halmos/src/halmos/sevm.py", line 1290, in init
    assert_address(addr)
  File "/Users/karma/projects/halmos/src/halmos/utils.py", line 478, in assert_address
    elif x.size() != 160:
         ^^^^^^
AttributeError: 'NoneType' object has no attribute 'size'
Symbolic test result: 0 passed; 1 failed; time: 0.03s

[time] total: 1.28s (build: 0.71s, load: 0.53s, tests: 0.04s)

failed() does the following:

    function failed() public returns (bool) {
        if (_failed) {
            return _failed;
        } else {
            bool globalFailed = false;
            if (hasHEVMContext()) {  // <- true since we return extcodesize 1 for the hevm address
                (, bytes memory retdata) = HEVM_ADDRESS.call(
                    abi.encodePacked(
                        bytes4(keccak256("load(address,bytes32)")),
                        abi.encode(HEVM_ADDRESS, bytes32("failed"))
                    )
                );
                globalFailed = abi.decode(retdata, (bool));  // <- fails during the handling of vm.load(hevm, 'failed')
            }
            return globalFailed;
        }
    }

In halmos, vm.load is handled like this:

        # vm.load(address,bytes32)
        elif funsig == hevm_cheat_code.load_sig:
            load_account = uint160(arg.get_word(4))  #  == 0x7109709ecfa91a80626ff3989d68f67f5b1dd12d
            load_slot = uint256(arg.get_word(36))
            load_account_alias = sevm.resolve_address_alias(
                ex, load_account, stack, step_id, branching=False
            )

            return ByteVec(sevm.sload(ex, load_account_alias, load_slot))

resolve_address_alias returns None for load_account=0x7109709ecfa91a80626ff3989d68f67f5b1dd12d, which leads to the downstream exception in sevm.sload

@karmacoma-eth karmacoma-eth added the bug Something isn't working label Aug 27, 2024
@daejunpark
Copy link
Collaborator

fyi, it's not ds-test any longer, it's the one in forge-std:

    function failed() public view returns (bool) {                                             
        if (_failed) {                                                                         
            return _failed;                                                                    
        } else {
            return vm.load(address(vm), bytes32("failed")) != bytes32(0);                      
        }   
    }

@daejunpark
Copy link
Collaborator

basically, address(vm) is non-existing, thus resolve_address_alias returns None, indicating the nonexistence.

for this particular problem, we can handle this similar to what we do for fail().

in general, it would be better to raise an exception when vm.load or vm.store is called for nonexisting account.

@daejunpark
Copy link
Collaborator

btw, in what context, is this failed() used? note that we immediately fail when fail() is called, so failed() will always return 0.

@karmacoma-eth
Copy link
Collaborator Author

btw, in what context, is this failed() used?

I'm not sure how we get there, this was a minimal repro I extract from a more complicated setup in damn-vulnerable-defi

@igorganich
Copy link

igorganich commented Aug 27, 2024

btw, in what context, is this failed() used?

I set the contract as "contract << contractName >> is ..., Test" to use vm.assume() inside of contract function
Then, I called abstract call to this contract (contract_address.call(data)) and one of the paths was failed() call

@daejunpark
Copy link
Collaborator

btw, in what context, is this failed() used?

I set the contract as "contract << contractName >> is ..., Test" to use vm.assume() inside of contract function Then, I called abstract call to this contract (contract_address.call(data)) and one of the paths was failed() call

i see. now i see what was going on. thanks for the context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants
@daejunpark @igorganich @karmacoma-eth and others