Skip to content

Commit 45ca5ac

Browse files
authored
Merge pull request #22 from rainshowerLabs/0.5.0
0.5.0 release
2 parents 2c0e17b + 9619a02 commit 45ca5ac

File tree

16 files changed

+896
-824
lines changed

16 files changed

+896
-824
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/target
2+
/.venv

Cargo.lock

Lines changed: 19 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
[package]
22
name = "sothis"
3-
version = "0.4.0"
3+
version = "0.5.0"
44
edition = "2021"
55
authors = ["makemake <vukasin@gostovic.me>"]
6-
license = "AGPL-3.0-only"
6+
license = "MPL-2.0"
77
description = "Tool for replaying historical EVM state."
88
readme = "README.md"
9-
homepage = "https://github.com/makemake-kbo/sothis"
10-
repository = "https://github.com/makemake-kbo/sothis"
9+
homepage = "https://github.com/rainshowerLabs/sothis"
10+
repository = "https://github.com/rainshowerLabs/sothis"
1111
keywords = ["cli", "ethereum", "foundry", "reth", "revm"]
1212
categories = ["command-line-utilities"]
13-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
1413

1514
[dependencies]
1615
clap = "4.3.0"
1716
ctrlc = "3.4.0"
1817
ethers = {version = "2.0.7", features = ["legacy"]}
18+
regex = "1.9.1"
1919
reqwest = { version = "0.11.18", features = ["blocking", "json"] }
2020
serde = { version = "1.0.163", features = ["derive"] }
2121
serde_json = "1.0.96"

LICENSE

Lines changed: 373 additions & 661 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ Sothis is a tool for replaying historical state on a local ***anvil/hardhat*** t
1010

1111
Join the [Rainshower Labs discord server](https://discord.gg/Cs3h397gkz) to discuss sothis and get help.
1212

13+
## Using sothis as a crate
14+
15+
Sothis can be used as a crate in other rust projects. All tracking modes, as well as the lightweight wrapper around JSON-RPC calls can be used. For more on using sothis as a crate, check out the [wiki.](https://github.com/rainshowerLabs/sothis/wiki)
16+
17+
We are targeting **1.68.2 as the MSRV**(minimum supported rust version).
18+
1319
## Usage
1420

1521
Sothis has optional arguments that are not listed in their respective mode sections that might prove useful. Please study the help section below. You can view it any time by running `sothis --help`.
@@ -23,7 +29,7 @@ Options:
2329
-r, --replay_rpc <replay_rpc>...
2430
HTTP JSON-RPC of the node we're replaying data to
2531
-m, --mode <mode>...
26-
Choose between live, historic, track, or fast_track [default: historic]
32+
Choose between live, historic, track, fast_track, or call_track [default: historic]
2733
-b, --terminal_block <terminal_block>...
2834
Block we're replaying until
2935
--exit_on_tx_fail [<exit_on_tx_fail>...]
@@ -38,11 +44,17 @@ Options:
3844
Exit the program if a transaction fails
3945
--no_setup [<no_setup>...]
4046
Start replaying immediately.
47+
--decimal [<decimal>...]
48+
Start replaying immediately.
4149
-c, --contract_address <contract_address>...
4250
Address of the contract we're tracking storage.
4351
-l, --storage_slot <storage_slot>...
4452
Storage slot for the variable we're tracking
45-
-0, --origin_block <origin_block>...
53+
-a, --calldata <calldata>...
54+
Storage slot for the variable we're tracking
55+
-o, --origin_block <origin_block>...
56+
First block sothis will look at.
57+
-q, --query_interval <query_interval>...
4658
First block sothis will look at.
4759
-p, --path <path>...
4860
Path to file we're writing to [default: .]
@@ -52,9 +64,10 @@ Options:
5264
Print help
5365
-V, --version
5466
Print version
67+
5568
```
5669

57-
Sothis currently has 4 modes. Live, historic, track, and fast track.
70+
Sothis currently has 5 modes. Live, historic, track, fast track, and call track.
5871

5972
### Historic
6073

@@ -139,6 +152,27 @@ Once you are done tracking the slot, terminate the process via a `SIGTERM` or a
139152
`sothis --mode track --source_rpc http://localhost:8545 --contract_address 0x910cbd523d972eb0a6f4cae4618ad62622b39dbf --storage_slot 3 --filename siuuu.json --path ~/Desktop
140153
`
141154

155+
156+
### Call track
157+
158+
The fast call mode is used to track the change for a *historic* eth_call. It cannot be used to get a live view of it. The source_rpc must be an archive node for this mode to perform optimally. This can be used to get historic chainlink oracle prices, see the output of decentralzied exchange swaps over time, and more.
159+
160+
#### Usage
161+
162+
- `--mode call_track`: Used to denote we are using the tracking mode.
163+
- `--source_rpc`: RPC of the node we are getting data from.
164+
- `--contract_address`: Address of the contract we'll be calling.
165+
- `--calldata`: Calldata we're using.
166+
- `--origin_block`: The block from which we start tracking.
167+
- `--terminal_block`(optional): Final block sothis will track. If not specified, sothis will track until terminated.
168+
- `--filename`(optional): Name of our output file. The default filename is formatted as: `address-{}-slot-{}-timestamp-{}.json`.
169+
- `--path`(optional): Path to our output file. The default path is the current directory.
170+
171+
Once you are done tracking the slot, terminate the process via a `SIGTERM` or a `SIGINT` (ctrl-c), which will terminate execution and write the file. Keep in mind that sothis will check once per new block if you tried to terminate it. If no new block are produced on the source_rpc, sothis will not terminate and nothing will be written if you force close it. The example below demonstrates tracking of the historic chainlink oracle price for ETH/USD on mainnet.
172+
173+
`sothis --mode call_track --source_rpc http://localhost:8545 --contract_address 0x1c479675ad559DC151F6Ec7ed3FbF8ceE79582B6 --origin_block 17799350 --calldata 0x06f13056
174+
`
175+
142176
## Installation
143177

144178
Sothis is a rust crate. You can install it with cargo:

convert_to_dec.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import sys
2+
import re
3+
4+
def is_valid_eth_address(hex_str):
5+
return len(hex_str) == 42 and hex_str[:2] == "0x" and all(c in "0123456789abcdefABCDEF" for c in hex_str[2:])
6+
7+
def hex_to_decimal(match):
8+
hex_str = match.group(0)
9+
if is_valid_eth_address(hex_str):
10+
return hex_str # Ignore valid Ethereum addresses
11+
decimal_num = str(int(hex_str, 16))
12+
return decimal_num
13+
14+
def convert_hex_to_decimal_in_file(file_path):
15+
try:
16+
with open(file_path, 'r') as file:
17+
content = file.read()
18+
19+
# Use regular expression to find all hexadecimal numbers in the content
20+
pattern = r'0x[0-9A-Fa-f]+'
21+
converted_content = re.sub(pattern, hex_to_decimal, content)
22+
23+
with open(file_path, 'w') as file:
24+
file.write(converted_content)
25+
26+
print(f"Conversion successful. Hex numbers in '{file_path}' (excluding Ethereum addresses) converted to decimal.")
27+
except FileNotFoundError:
28+
print(f"Error: File '{file_path}' not found.")
29+
except Exception as e:
30+
print(f"Error occurred: {e}")
31+
32+
if __name__ == "__main__":
33+
if len(sys.argv) != 2:
34+
print("Usage: python script.py <file_path>")
35+
else:
36+
file_path = sys.argv[1]
37+
convert_hex_to_decimal_in_file(file_path)

src/cli_arg.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use clap::{Command, Arg};
2+
3+
4+
// This is not the recommended way to set clap args but it works and its too late to change it now
5+
pub fn create_match() -> clap::Command {
6+
let matches = Command::new("sothis")
7+
.version("0.5.0")
8+
.author("makemake <vukasin@gostovic.me>")
9+
.about("Tool for replaying historical transactions. Designed to be used with anvil or hardhat.")
10+
.arg(Arg::new("source_rpc")
11+
.long("source_rpc")
12+
.short('s')
13+
.num_args(1..)
14+
.required(true)
15+
.help("HTTP JSON-RPC of the node we're querying data from"))
16+
.arg(Arg::new("replay_rpc")
17+
.long("replay_rpc")
18+
.short('r')
19+
.num_args(1..)
20+
.help("HTTP JSON-RPC of the node we're replaying data to"))
21+
.arg(Arg::new("mode")
22+
.long("mode")
23+
.short('m')
24+
.num_args(1..)
25+
.default_value("historic")
26+
.help("Choose between live, historic, track, fast_track, or call_track"))
27+
.arg(Arg::new("terminal_block")
28+
.long("terminal_block")
29+
.short('b')
30+
.num_args(1..)
31+
.required_if_eq("mode", "historic")
32+
.help("Block we're replaying until"))
33+
.arg(Arg::new("exit_on_tx_fail")
34+
.long("exit_on_tx_fail")
35+
.num_args(0..)
36+
.help("Exit the program if a transaction fails"))
37+
.arg(Arg::new("block_listen_time")
38+
.long("block_listen_time")
39+
.short('t')
40+
.num_args(1..)
41+
.default_value("500")
42+
.help("Time in ms to check for new blocks."))
43+
.arg(Arg::new("entropy_threshold")
44+
.long("entropy_threshold")
45+
.num_args(1..)
46+
.default_value("0.07")
47+
.help("Set the percentage of failed transactions to trigger a warning"))
48+
.arg(Arg::new("replay_delay")
49+
.long("replay_delay")
50+
.short('d')
51+
.num_args(1..)
52+
.default_value("0")
53+
.help("Default delay for block replay in ms"))
54+
.arg(Arg::new("send_as_unsigned")
55+
.long("send_as_unsigned")
56+
.num_args(0..)
57+
.help("Exit the program if a transaction fails"))
58+
.arg(Arg::new("no_setup")
59+
.long("no_setup")
60+
.num_args(0..)
61+
.help("Start replaying immediately."))
62+
.arg(Arg::new("decimal")
63+
.long("decimal")
64+
.num_args(0..)
65+
.help("Start replaying immediately."))
66+
.arg(Arg::new("contract_address")
67+
.long("contract_address")
68+
.short('c')
69+
.num_args(1..)
70+
.required_if_eq("mode", "track")
71+
.required_if_eq("mode", "fast_track")
72+
.help("Address of the contract we're tracking storage."))
73+
.arg(Arg::new("storage_slot")
74+
.long("storage_slot")
75+
.short('l')
76+
.num_args(1..)
77+
.required_if_eq("mode", "track")
78+
.required_if_eq("mode", "fast_track")
79+
.help("Storage slot for the variable we're tracking"))
80+
.arg(Arg::new("calldata")
81+
.long("calldata")
82+
.short('a')
83+
.num_args(1..)
84+
.required_if_eq("mode", "call_track")
85+
.help("Storage slot for the variable we're tracking"))
86+
.arg(Arg::new("origin_block")
87+
.long("origin_block")
88+
.short('o')
89+
.num_args(1..)
90+
.required_if_eq("mode", "fast_track")
91+
.help("First block sothis will look at."))
92+
.arg(Arg::new("query_interval")
93+
.long("query_interval")
94+
.short('q')
95+
.num_args(1..)
96+
.help("First block sothis will look at."))
97+
.arg(Arg::new("path")
98+
.long("path")
99+
.short('p')
100+
.num_args(1..)
101+
.default_value(".")
102+
.help("Path to file we're writing to"))
103+
.arg(Arg::new("filename")
104+
.long("filename")
105+
.short('f')
106+
.num_args(1..)
107+
.default_value("")
108+
.help("Name of the file."));
109+
110+
return matches;
111+
}

0 commit comments

Comments
 (0)