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

[Bug-Candidate]: A simple contract took onchain fuzzer extremely long time to run due to zero calls per second #1261

Closed
viper7882 opened this issue May 19, 2024 · 2 comments

Comments

@viper7882
Copy link

viper7882 commented May 19, 2024

Describe the issue:

Hi admin,

Besides Long time fetching slots reported here, for unknown reason it took extraordinary time to run onchain fuzzing on the even simpler test contract. Essentially there is only one uint256[] memory function and optimization function. The calls/s is always 0. I'm uncertain if zero calls per second is related to larger than normal number of slots fetched for this contract. As I have no control over number of slots fetched, it is best for you to investigate.

I didn't see similar issue on other contracts with uint256[] memory function. Hopefully you could shed some light on what's going on in the fuzzer for this simple contract.

Code example to reproduce the issue:

echidna-config.yaml

contractAddr: '0xb4c79daB8f259C7Aee6E5b2Aa729821864227e84'
rpcBlock: 19305256
rpcUrl: https://eth.drpc.org/
seed: 6368
shrinkLimit: 3000
solcArgs: via-ir
testDestruction: true
testLimit: 100000
testMode: optimization
workers: 12

Hevm.sol

// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

interface IHevm {
    // Set block.timestamp to newTimestamp
    function warp(uint256 newTimestamp) external;

    // Set block.number to newNumber
    function roll(uint256 newNumber) external;

    // Loads a storage slot from an address
    function load(address where, bytes32 slot) external returns (bytes32);

    // Stores a value to an address' storage slot
    function store(address where, bytes32 slot, bytes32 value) external;

    // Signs data (privateKey, digest) => (r, v, s)
    function sign(
        uint256 privateKey,
        bytes32 digest
    ) external returns (uint8 r, bytes32 v, bytes32 s);

    // Gets address for a given private key
    function addr(uint256 privateKey) external returns (address addr);

    // Performs a foreign function call via terminal
    function ffi(
        string[] calldata inputs
    ) external returns (bytes memory result);

    // Performs the next smart contract call with specified `msg.sender`
    function prank(address newSender) external;
}

IHevm constant cheats = IHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

test.sol

pragma solidity ^0.8.0;

// Shared library
import "./Hevm.sol";

contract DSTest {
    event log                    (string);
    event logs                   (bytes);

    event log_address            (address);
    event log_bytes32            (bytes32);
    event log_int                (int);
    event log_uint               (uint);
    event log_bytes              (bytes);
    event log_string             (string);

    event log_named_address      (string key, address val);
    event log_named_bytes32      (string key, bytes32 val);
    event log_named_decimal_int  (string key, int val, uint decimals);
    event log_named_decimal_uint (string key, uint val, uint decimals);
    event log_named_int          (string key, int val);
    event log_named_uint         (string key, uint val);
    event log_named_bytes        (string key, bytes val);
    event log_named_string       (string key, string val);

    bool public IS_TEST = true;
    bool private _failed;

    address constant HEVM_ADDRESS =
        address(bytes20(uint160(uint256(keccak256('hevm cheat code')))));

    modifier mayRevert() { _; }
    modifier testopts(string memory) { _; }

    function failed() public returns (bool) {
        if (_failed) {
            return _failed;
        } else {
            bool globalFailed = false;
            if (hasHEVMContext()) {
                (, bytes memory retdata) = HEVM_ADDRESS.call(
                    abi.encodePacked(
                        bytes4(keccak256("load(address,bytes32)")),
                        abi.encode(HEVM_ADDRESS, bytes32("failed"))
                    )
                );
                globalFailed = abi.decode(retdata, (bool));
            }
            return globalFailed;
        }
    }

    function fail() internal virtual {
        if (hasHEVMContext()) {
            (bool status, ) = HEVM_ADDRESS.call(
                abi.encodePacked(
                    bytes4(keccak256("store(address,bytes32,bytes32)")),
                    abi.encode(HEVM_ADDRESS, bytes32("failed"), bytes32(uint256(0x01)))
                )
            );
            status; // Silence compiler warnings
        }
        _failed = true;
    }

    function hasHEVMContext() internal view returns (bool) {
        uint256 hevmCodeSize = 0;
        assembly {
            hevmCodeSize := extcodesize(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D)
        }
        return hevmCodeSize > 0;
    }

    modifier logs_gas() {
        uint startGas = gasleft();
        _;
        uint endGas = gasleft();
        emit log_named_uint("gas", startGas - endGas);
    }
}

interface IGebUniswapV3KeeperFlashProxyETH {
    receive() external payable;

    function bid(uint256 auctionId, uint256 amount) external;

    function liquidateAndSettleSAFE(address safe) external returns (uint256 auction);

    function multipleBid(uint256[] memory auctionIds, uint256[] memory amounts) external;

    function settleAuction(uint256 auctionId) external;

    function settleAuction(uint256[] memory auctionIds) external;

    function uniswapV3SwapCallback(int256 _amount0, int256 _amount1, bytes memory _data) external;

    function ONE() external view returns (uint256 unknown_ret_var_1);

    function ZERO() external view returns (uint256 unknown_ret_var_1);

    function auctionHouse() external view returns (address unknown_ret_var_1);

    function caller() external view returns (address unknown_ret_var_1);

    function coin() external view returns (address unknown_ret_var_1);

    function coinJoin() external view returns (address unknown_ret_var_1);

    function collateralType() external view returns (bytes32 unknown_ret_var_1);

    function ethJoin() external view returns (address unknown_ret_var_1);

    function liquidationEngine() external view returns (address unknown_ret_var_1);

    function safeEngine() external view returns (address unknown_ret_var_1);

    function uniswapPair() external view returns (address unknown_ret_var_1);

    function weth() external view returns (address unknown_ret_var_1);
}

contract test is DSTest {
    IGebUniswapV3KeeperFlashProxyETH constant GebUniswapv3KeeperFlashProxyETH =
        IGebUniswapV3KeeperFlashProxyETH(payable(0xcDCE3aF4ef75bC89601A2E785172c6B9f65a0aAc));
    IGebUniswapV3KeeperFlashProxyETH victim_contract_object = GebUniswapv3KeeperFlashProxyETH;
    address public initial_victim_auctionHouse_owner = victim_contract_object.auctionHouse();

    constructor() payable {
        // WARNING: Value must be in sync with echidna-config.yaml
        // Block Number
        cheats.roll(19305256);
        // Block Timestamp
        cheats.warp(1708872023);
    }

    function victim_settleAuction(uint256[] memory auctionIds) public {
        victim_contract_object.settleAuction(auctionIds);
    }

    function echidna_optimize__victim_auctionHouse_owner() public returns (bool) {
        // Condition of vulnerability
        return initial_victim_auctionHouse_owner != victim_contract_object.auctionHouse();
    }
}

Command:

echidna test.sol --contract test --config echidna-config.yaml

Version:

Echidna 2.2.2
0.10.1

Relevant log output:

Observation: While the Calls/s remains at 0, the number of Fetched Slots are keep increasing as time passing by.
GebUniswapV3KeeperFlashProxyETH_zero_calls_per_seconds_extremely_long_fuzzing_time

@ggrieco-tob
Copy link
Member

This is expected, as the settleAuction will fetch a lot of slots in each sequence:

    /// @notice Settle auctions
    /// @param auctionIds IDs of the auctions to be settled
    function settleAuction(uint[] memory auctionIds) public {
        (uint[] memory ids, uint[] memory bidAmounts, uint totalAmount) = _getOpenAuctionsBidSizes(auctionIds);
        require(totalAmount > ZERO, "GebUniswapV3KeeperFlashProxyETH/all-auctions-already-settled");

        bytes memory callbackData = abi.encodeWithSelector(this.multipleBid.selector, ids, bidAmounts);

        _startSwap(totalAmount, callbackData);
    }

It seems the correct way to run this will be to either use a local node or optimize the fuzzing to select valid auction numbers for the list.

@viper7882
Copy link
Author

I see, thank you @ggrieco-tob for your help. Closing this issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants