1+ use std:: { io, str:: FromStr } ;
2+
13use cast:: Cast ;
24use clap:: Parser ;
3- use ethers:: {
5+ use ethers:: { providers:: Middleware , types:: NameOrAddress } ;
6+ use ethers_core:: {
47 abi:: { Address , Event , RawTopicFilter , Topic , TopicFilter } ,
5- providers:: Middleware ,
6- types:: { BlockId , BlockNumber , Filter , FilterBlockOption , NameOrAddress , ValueOrArray , H256 } ,
8+ types:: { BlockId , BlockNumber , Filter , FilterBlockOption , ValueOrArray , H256 } ,
79} ;
810use eyre:: Result ;
911use foundry_cli:: { opts:: EthereumOpts , utils} ;
1012use foundry_common:: abi:: { get_event, parse_tokens} ;
1113use foundry_config:: Config ;
1214use itertools:: Itertools ;
13- use std:: str:: FromStr ;
1415
1516/// CLI arguments for `cast logs`.
1617#[ derive( Debug , Parser ) ]
@@ -19,32 +20,37 @@ pub struct LogsArgs {
1920 ///
2021 /// Can also be the tags earliest, finalized, safe, latest, or pending.
2122 #[ clap( long) ]
22- from_block : Option < BlockId > ,
23+ pub from_block : Option < BlockId > ,
2324
2425 /// The block height to stop query at.
2526 ///
2627 /// Can also be the tags earliest, finalized, safe, latest, or pending.
2728 #[ clap( long) ]
28- to_block : Option < BlockId > ,
29+ pub to_block : Option < BlockId > ,
2930
3031 /// The contract address to filter on.
3132 #[ clap(
3233 long,
3334 value_parser = NameOrAddress :: from_str
3435 ) ]
35- address : Option < NameOrAddress > ,
36+ pub address : Option < NameOrAddress > ,
3637
3738 /// The signature of the event to filter logs by which will be converted to the first topic or
3839 /// a topic to filter on.
3940 #[ clap( value_name = "SIG_OR_TOPIC" ) ]
40- sig_or_topic : Option < String > ,
41+ pub sig_or_topic : Option < String > ,
4142
4243 /// If used with a signature, the indexed fields of the event to filter by. Otherwise, the
4344 /// remaining topics of the filter.
4445 #[ clap( value_name = "TOPICS_OR_ARGS" ) ]
45- topics_or_args : Vec < String > ,
46+ pub topics_or_args : Vec < String > ,
47+
48+ /// If the RPC type and endpoints supports `eth_subscribe` stream logs instead of printing and
49+ /// exiting. Will continue until interrupted or TO_BLOCK is reached.
50+ #[ clap( long) ]
51+ pub subscribe : bool ,
4652
47- /// Print the logs as JSON.
53+ /// Print the logs as JSON.s
4854 #[ clap( long, short, help_heading = "Display options" ) ]
4955 json : bool ,
5056
@@ -55,12 +61,21 @@ pub struct LogsArgs {
5561impl LogsArgs {
5662 pub async fn run ( self ) -> Result < ( ) > {
5763 let LogsArgs {
58- from_block, to_block, address, topics_or_args, sig_or_topic, json, eth, ..
64+ from_block,
65+ to_block,
66+ address,
67+ sig_or_topic,
68+ topics_or_args,
69+ subscribe,
70+ json,
71+ eth,
5972 } = self ;
6073
6174 let config = Config :: from ( & eth) ;
6275 let provider = utils:: get_provider ( & config) ?;
6376
77+ let cast = Cast :: new ( & provider) ;
78+
6479 let address = match address {
6580 Some ( address) => {
6681 let address = match address {
@@ -72,48 +87,34 @@ impl LogsArgs {
7287 None => None ,
7388 } ;
7489
75- let from_block = convert_block_number ( & provider, from_block) . await ?;
76- let to_block = convert_block_number ( & provider, to_block) . await ?;
77-
78- let cast = Cast :: new ( & provider) ;
90+ let from_block = cast. convert_block_number ( from_block) . await ?;
91+ let to_block = cast. convert_block_number ( to_block) . await ?;
7992
8093 let filter = build_filter ( from_block, to_block, address, sig_or_topic, topics_or_args) ?;
8194
82- let logs = cast. filter_logs ( filter, json) . await ?;
95+ if !subscribe {
96+ let logs = cast. filter_logs ( filter, json) . await ?;
97+
98+ println ! ( "{}" , logs) ;
8399
84- println ! ( "{}" , logs) ;
100+ return Ok ( ( ) )
101+ }
102+
103+ let mut stdout = io:: stdout ( ) ;
104+ cast. subscribe ( filter, & mut stdout, json, ctrl_c_future ( ) ) . await ?;
85105
86106 Ok ( ( ) )
87107 }
88108}
89109
90- /// Converts a block identifier into a block number.
91- ///
92- /// If the block identifier is a block number, then this function returns the block number. If the
93- /// block identifier is a block hash, then this function returns the block number of that block
94- /// hash. If the block identifier is `None`, then this function returns `None`.
95- async fn convert_block_number < M : Middleware > (
96- provider : M ,
97- block : Option < BlockId > ,
98- ) -> Result < Option < BlockNumber > , eyre:: Error >
99- where
100- M :: Error : ' static ,
101- {
102- match block {
103- Some ( block) => match block {
104- BlockId :: Number ( block_number) => Ok ( Some ( block_number) ) ,
105- BlockId :: Hash ( hash) => {
106- let block = provider. get_block ( hash) . await ?;
107- Ok ( block. map ( |block| block. number . unwrap ( ) ) . map ( BlockNumber :: from) )
108- }
109- } ,
110- None => Ok ( None ) ,
111- }
110+ async fn ctrl_c_future ( ) -> Result < ( ) > {
111+ tokio:: signal:: ctrl_c ( ) . await ?;
112+ Ok ( ( ) )
112113}
113114
114- // First tries to parse the `sig_or_topic` as an event signature. If successful, `topics_or_args` is
115- // parsed as indexed inputs and converted to topics. Otherwise, `sig_or_topic` is prepended to
116- // `topics_or_args` and used as raw topics.
115+ /// Builds a Filter by first trying to parse the `sig_or_topic` as an event signature. If
116+ /// successful, `topics_or_args` is parsed as indexed inputs and converted to topics. Otherwise,
117+ /// `sig_or_topic` is prepended to `topics_or_args` and used as raw topics.
117118fn build_filter (
118119 from_block : Option < BlockNumber > ,
119120 to_block : Option < BlockNumber > ,
@@ -154,7 +155,7 @@ fn build_filter(
154155 Ok ( filter)
155156}
156157
157- // Creates a TopicFilter for the given event signature and arguments.
158+ /// Creates a TopicFilter from the given event signature and arguments.
158159fn build_filter_event_sig ( event : Event , args : Vec < String > ) -> Result < TopicFilter , eyre:: Error > {
159160 let args = args. iter ( ) . map ( |arg| arg. as_str ( ) ) . collect :: < Vec < _ > > ( ) ;
160161
@@ -195,7 +196,7 @@ fn build_filter_event_sig(event: Event, args: Vec<String>) -> Result<TopicFilter
195196 Ok ( event. filter ( raw) ?)
196197}
197198
198- // Creates a TopicFilter from raw topic hashes.
199+ /// Creates a TopicFilter from raw topic hashes.
199200fn build_filter_topics ( topics : Vec < String > ) -> Result < TopicFilter , eyre:: Error > {
200201 let mut topics = topics
201202 . into_iter ( )
@@ -214,8 +215,11 @@ fn build_filter_topics(topics: Vec<String>) -> Result<TopicFilter, eyre::Error>
214215
215216#[ cfg( test) ]
216217mod tests {
218+ use std:: str:: FromStr ;
219+
217220 use super :: * ;
218221 use ethers:: types:: H160 ;
222+ use ethers_core:: types:: H256 ;
219223
220224 const ADDRESS : & str = "0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38" ;
221225 const TRANSFER_SIG : & str = "Transfer(address indexed,address indexed,uint256)" ;
0 commit comments