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: JSONDecodeError crash when running hybrid-echidna #93

Open
rappie opened this issue Dec 4, 2022 · 2 comments
Open

Bug: JSONDecodeError crash when running hybrid-echidna #93

rappie opened this issue Dec 4, 2022 · 2 comments
Labels
bug Something isn't working

Comments

@rappie
Copy link

rappie commented Dec 4, 2022

Describe the bug
It runs echidna for about 2 minutes and then crashes.

To Reproduce
Sadly I cannot share my environment. I could narrowing things down with some guidance.

Additional context:

  • Manjaro Linux, Python 3.10.8
  • Echidna 2.0.4
  • Slither 0.9.1
❯ hybrid-echidna . --contract E2E --config echidna-config.yaml
Traceback (most recent call last):
  File "/home/rappie/.local/bin/hybrid-echidna", line 8, in <module>
    sys.exit(main())
  File "/home/rappie/.local/lib/python3.10/site-packages/optik/echidna/__main__.py", line 554, in main
    func(sys.argv[1:])
  File "/home/rappie/.local/lib/python3.10/site-packages/optik/echidna/__main__.py", line 344, in run_hybrid_echidna_with_display
    raise exc
  File "/home/rappie/.local/lib/python3.10/site-packages/optik/echidna/__main__.py", line 321, in run_hybrid_echidna_with_display
    run_hybrid_echidna(args)
  File "/home/rappie/.local/lib/python3.10/site-packages/optik/echidna/__main__.py", line 235, in run_hybrid_echidna
    nb_cov_insts = count_unique_pc(p.stdout)
  File "/home/rappie/.local/lib/python3.10/site-packages/optik/echidna/interface.py", line 523, in count_unique_pc
    data = json.loads(output)
  File "/usr/lib/python3.10/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
  File "/usr/lib/python3.10/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/lib/python3.10/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
@rappie rappie added the bug Something isn't working label Dec 4, 2022
@SheldonHolmgren
Copy link

I had the same issue. I managed to fix it by completely uninstalling nix (https://nix-tutorial.gitlabpages.inria.fr/nix-tutorial/installation.html#uninstalling-nix) and downloading the echidna-test v2.0.4 binary from https://github.com/crytic/echidna/releases

@SheldonHolmgren
Copy link

I see what's going on. In https://github.com/crytic/optik/blob/master/optik/echidna/interface.py#L522 optik assumes that echidna-test --format json will have a json in the second line of the output. But starting from commit 1f59ae189258ab52342331a92208f5b6b8930ea7 the json is in the third line of echidna's output.

The following fix works:

--- a/optik/echidna/interface.py
+++ b/optik/echidna/interface.py
@@ -469,7 +469,7 @@ def extract_cases_from_json_output(output: str) -> List[List[str]]:
     """
     # Sometimes the JSON output starts with a line such as
     # "Loaded total of 500 transactions from /tmp/c4/coverage"
-    if output.startswith("Loaded total of"):
+    while output.startswith("Loaded total of"):
         output = output.split("\n", 1)[1]
     data = json.loads(output)
     if "tests" not in data:
@@ -518,7 +518,7 @@ def count_unique_pc(output: str) -> int:
 
     :param output: echidna's JSON output as a simple string
     """
-    if output.startswith("Loaded total of"):
+    while output.startswith("Loaded total of"):
         output = output.split("\n", 1)[1]
     data = json.loads(output)
     res = 0

But that's not sufficient to make echidna v2.0.5 work. The json format has also changed so the following dirty fix is needed:

--- a/optik/corpus/generator.py
+++ b/optik/corpus/generator.py
@@ -108,10 +108,10 @@ class EchidnaCorpusGenerator(CorpusGenerator):
             with open(os.path.join(corpus_dir, filename), "rb") as f:
                 data = json.loads(f.read())
                 for tx in data:
-                    if tx["_call"]["tag"] == "NoCall":
+                    if tx["call"]["tag"] == "NoCall":
                         continue
                     func_name, args_spec, _ = extract_func_from_call(
-                        tx["_call"]
+                        tx["call"]
                     )
                     func_prototype = func_signature(func_name, args_spec)
                     # Only store one tx for each function
diff --git a/optik/echidna/interface.py b/optik/echidna/interface.py
index ca23622..3f580b8 100644
--- a/optik/echidna/interface.py
+++ b/optik/echidna/interface.py
@@ -168,13 +168,13 @@ def load_tx(tx: Dict, tx_name: str = "") -> AbstractTx:
     # Translate block number/timestamp increments
     block_num_inc = Var(256, f"{tx_name}_block_num_inc")
     block_timestamp_inc = Var(256, f"{tx_name}_block_timestamp_inc")
-    ctx.set(block_num_inc.name, int(tx["_delay"][1], 16), block_num_inc.size)
+    ctx.set(block_num_inc.name, int(tx["delay"][1], 16), block_num_inc.size)
     ctx.set(
-        block_timestamp_inc.name, int(tx["_delay"][0], 16), block_num_inc.size
+        block_timestamp_inc.name, int(tx["delay"][0], 16), block_num_inc.size
     )
 
     # Check if it's a "NoCall" echidna transaction
-    if tx["_call"]["tag"] == "NoCall":
+    if tx["call"]["tag"] == "NoCall":
         return AbstractTx(
             None,
             block_num_inc,
@@ -183,27 +183,27 @@ def load_tx(tx: Dict, tx_name: str = "") -> AbstractTx:
         )
 
     # Translate function call
-    func_name, args_spec, arg_values = extract_func_from_call(tx["_call"])
+    func_name, args_spec, arg_values = extract_func_from_call(tx["call"])
     call_data = function_call(func_name, args_spec, ctx, tx_name, *arg_values)
 
     # Translate message sender
     sender = Var(160, f"{tx_name}_sender")
-    ctx.set(sender.name, int(tx["_src"], 16), sender.size)
+    ctx.set(sender.name, int(tx["src"], 16), sender.size)
 
     # Translate message value
     # Echidna will only send non-zero msg.value to payable funcs
     # so we only make an abstract value in that case
-    if int(tx["_value"], 16) != 0:
+    if int(tx["value"], 16) != 0:
         value = Var(256, f"{tx_name}_value")
-        ctx.set(value.name, int(tx["_value"], 16), value.size)
+        ctx.set(value.name, int(tx["value"], 16), value.size)
     else:
         value = Cst(256, 0)
 
     # Build transaction
     # TODO: make EVMTransaction accept integers as arguments
-    gas_limit = Cst(256, int(tx["_gas'"], 16))
-    gas_price = Cst(256, int(tx["_gasprice'"], 16))
-    recipient = int(tx["_dst"], 16)
+    gas_limit = Cst(256, tx["gas"], 16)
+    gas_price = Cst(256, int(tx["gasprice"], 16))
+    recipient = int(tx["dst"], 16)
     return AbstractTx(
         EVMTransaction(
             sender,  # origin
@@ -335,7 +335,7 @@ def update_tx(tx: Dict, new_model: VarContext, tx_name: str = "") -> Dict:
     tx = tx.copy()  # Copy transaction to avoid in-place modifications
 
     # Update call arguments
-    call = tx["_call"]
+    call = tx["call"]
     args = call["contents"][1]
     for i, arg in enumerate(args):
         update_argument(arg, f"{tx_name}_arg{i}", new_model)
@@ -344,20 +344,20 @@ def update_tx(tx: Dict, new_model: VarContext, tx_name: str = "") -> Dict:
     block_num_inc = f"{tx_name}_block_num_inc"
     block_timestamp_inc = f"{tx_name}_block_timestamp_inc"
     if new_model.contains(block_num_inc):
-        tx["_delay"][1] = hex(new_model.get(block_num_inc))
+        tx["delay"][1] = hex(new_model.get(block_num_inc))
     if new_model.contains(block_timestamp_inc):
-        tx["_delay"][0] = hex(new_model.get(block_timestamp_inc))
+        tx["delay"][0] = hex(new_model.get(block_timestamp_inc))
 
     # Update sender
     sender = f"{tx_name}_sender"
     if new_model.contains(sender):
         # Address so we need to pad it to 40 chars (20bytes)
-        tx["_src"] = f"0x{new_model.get(sender):0{40}x}"
+        tx["src"] = f"0x{new_model.get(sender):0{40}x}"
 
     # Update transaction value
     value = f"{tx_name}_value"
     if new_model.contains(value):
-        tx["_value"] = hex(new_model.get(value))
+        tx["value"] = hex(new_model.get(value))
 
     return tx

I will let someone more knowledgeable identify and implement a proper solution.

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

No branches or pull requests

2 participants