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 ) ]
@@ -44,7 +45,12 @@ pub struct LogsArgs {
4445 #[ clap( value_name = "TOPICS_OR_ARGS" ) ]
4546 topics_or_args : Vec < String > ,
4647
47- /// Print the logs as JSON.
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+ subscribe : bool ,
52+
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,29 @@ 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 ?;
8397
84- println ! ( "{}" , logs) ;
98+ println ! ( "{}" , logs) ;
8599
86- Ok ( ( ) )
87- }
88- }
100+ return Ok ( ( ) )
101+ }
89102
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 ) ,
103+ let mut stdout = io:: stdout ( ) ;
104+ cast. subscribe ( filter, & mut stdout, json) . await ?;
105+
106+ Ok ( ( ) )
111107 }
112108}
113109
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.
110+ /// Builds a Filter by first trying to parse the `sig_or_topic` as an event signature. If
111+ /// successful, `topics_or_args` is parsed as indexed inputs and converted to topics. Otherwise,
112+ /// `sig_or_topic` is prepended to `topics_or_args` and used as raw topics.
117113fn build_filter (
118114 from_block : Option < BlockNumber > ,
119115 to_block : Option < BlockNumber > ,
@@ -154,7 +150,7 @@ fn build_filter(
154150 Ok ( filter)
155151}
156152
157- // Creates a TopicFilter for the given event signature and arguments.
153+ /// Creates a TopicFilter from the given event signature and arguments.
158154fn build_filter_event_sig ( event : Event , args : Vec < String > ) -> Result < TopicFilter , eyre:: Error > {
159155 let args = args. iter ( ) . map ( |arg| arg. as_str ( ) ) . collect :: < Vec < _ > > ( ) ;
160156
@@ -195,7 +191,7 @@ fn build_filter_event_sig(event: Event, args: Vec<String>) -> Result<TopicFilter
195191 Ok ( event. filter ( raw) ?)
196192}
197193
198- // Creates a TopicFilter from raw topic hashes.
194+ /// Creates a TopicFilter from raw topic hashes.
199195fn build_filter_topics ( topics : Vec < String > ) -> Result < TopicFilter , eyre:: Error > {
200196 let mut topics = topics
201197 . into_iter ( )
@@ -214,8 +210,11 @@ fn build_filter_topics(topics: Vec<String>) -> Result<TopicFilter, eyre::Error>
214210
215211#[ cfg( test) ]
216212mod tests {
213+ use std:: str:: FromStr ;
214+
217215 use super :: * ;
218216 use ethers:: types:: H160 ;
217+ use ethers_core:: types:: H256 ;
219218
220219 const ADDRESS : & str = "0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38" ;
221220 const TRANSFER_SIG : & str = "Transfer(address indexed,address indexed,uint256)" ;
0 commit comments