11use {
2- crate :: utils:: { try_for, DangoBuilder } ,
2+ crate :: utils:: { try_for, DangoBuilder , SingleSignerExt } ,
33 dango_client:: { Secp256k1 , Secret , SingleSigner } ,
44 dango_genesis:: { GatewayOption , GenesisOption , HyperlaneOption } ,
5- dango_hyperlane_types:: isms:: multisig:: ValidatorSet ,
5+ dango_hyperlane_types:: {
6+ domain_hash, eip191_hash,
7+ isms:: { multisig:: ValidatorSet , HYPERLANE_DOMAIN_KEY } ,
8+ mailbox, multisig_hash, Addr32 ,
9+ } ,
610 dango_testing:: { constants:: user5, Preset } ,
711 dango_types:: {
812 config:: AppConfig ,
913 constants:: dango,
1014 gateway:: { self , Origin , Remote } ,
1115 } ,
1216 grug:: {
13- BroadcastClientExt , Coins , GasOption , QueryClientExt , ResultExt , SearchTxClient ,
14- __private:: hex_literal:: hex, addr, btree_map, btree_set,
17+ addr, btree_map, btree_set, Addr , Api , BroadcastClientExt , CheckedContractEvent , Coins ,
18+ EventName , FlatCommitmentStatus , GasOption , Hash256 , Inner , JsonDeExt , MockApi ,
19+ QueryClientExt , ResultExt , SearchEvent , SearchTxClient , __private:: hex_literal:: hex,
1520 } ,
1621 grug_indexer_client:: HttpClient ,
22+ manual_relay:: get_checkpoint_from_s3,
1723 std:: time:: Duration ,
1824 tracing:: Level ,
1925} ;
2026
27+ pub mod manual_relay;
2128pub mod utils;
2229
2330const PORT : u16 = 8080 ;
@@ -27,6 +34,8 @@ const PORT: u16 = 8080;
2734async fn run_dango ( ) -> anyhow:: Result < ( ) > {
2835 // --- SETTINGS ---
2936
37+ let local_domain = 88888867 ;
38+
3039 let routes = [ (
3140 Origin :: Local ( dango:: DENOM . clone ( ) ) ,
3241 Remote :: Warp {
@@ -61,7 +70,7 @@ async fn run_dango() -> anyhow::Result<()> {
6170 let mut ism_valset = HyperlaneOption :: preset_test ( ) . ism_validator_sets ;
6271 ism_valset. extend ( ism_validator_sets) ;
6372
64- DangoBuilder :: new ( "localdango" , 88888888 )
73+ DangoBuilder :: new ( "localdango" , local_domain )
6574 . with_block_creation ( grug:: BlockCreation :: Timed )
6675 . with_block_time ( grug:: Duration :: from_seconds ( 1 ) )
6776 . with_port ( PORT )
@@ -71,6 +80,7 @@ async fn run_dango() -> anyhow::Result<()> {
7180 ..Preset :: preset_test ( )
7281 } ,
7382 hyperlane : HyperlaneOption {
83+ local_domain,
7484 ism_validator_sets : ism_valset,
7585 ..Preset :: preset_test ( )
7686 } ,
@@ -87,6 +97,10 @@ async fn run_dango() -> anyhow::Result<()> {
8797async fn transfer_remote ( ) -> anyhow:: Result < ( ) > {
8898 // --- SETTINGS ---
8999
100+ let run_recover_address = true ;
101+ let location = "s3://hyperlane-testnet-val1/eu-north-1" ;
102+ let recover_address_should_be = addr ! ( "6603760598E4aAc3E9D47569cc3A7024cDa7003a" ) ;
103+
90104 let url = format ! ( "http://localhost:{}" , PORT ) ;
91105 let denom = dango:: DENOM . clone ( ) ;
92106 let amount = 55 ;
@@ -96,18 +110,20 @@ async fn transfer_remote() -> anyhow::Result<()> {
96110 } ;
97111 let recipient = addr ! ( "f63130398dE6467a539020ac2B6d876B7A850C5F" ) ;
98112
113+ // --- SETTINGS ---
114+
99115 let dango_client = HttpClient :: new ( url) ?;
100116
101117 let cfg: AppConfig = dango_client. query_app_config ( None ) . await ?;
102118 let chain_id = dango_client. query_status ( None ) . await ?. chain_id ;
103119
104- let mut user5 = SingleSigner :: new (
105- addr ! ( "a20a0e1a71b82d50fc046bc6e3178ad0154fd184" ) ,
120+ let mut user5 = SingleSigner :: new_first_account (
121+ & dango_client ,
106122 Secp256k1 :: from_bytes ( user5:: PRIVATE_KEY ) ?,
123+ Some ( & cfg) ,
107124 )
108- . with_query_nonce ( & dango_client)
109125 . await ?
110- . with_query_user_index ( & dango_client)
126+ . with_query_nonce ( & dango_client)
111127 . await ?;
112128
113129 let res = dango_client
@@ -129,11 +145,82 @@ async fn transfer_remote() -> anyhow::Result<()> {
129145 Duration :: from_millis ( 100 ) ,
130146 || async { dango_client. search_tx ( res. tx_hash ) . await } ,
131147 )
132- . await ?;
148+ . await ?
149+ . outcome
150+ . should_succeed ( ) ;
151+
152+ if !run_recover_address {
153+ return Ok ( ( ) ) ;
154+ }
155+
156+ let dispatch = outcome
157+ . events
158+ . clone ( )
159+ . search_event :: < CheckedContractEvent > ( )
160+ . with_commitment_status ( FlatCommitmentStatus :: Committed )
161+ . with_predicate ( move |e| {
162+ & e. contract == & cfg. addresses . hyperlane . mailbox && e. ty == mailbox:: Dispatch :: EVENT_NAME
163+ } )
164+ . take ( )
165+ . one ( )
166+ . event
167+ . data
168+ . deserialize_json :: < mailbox:: Dispatch > ( ) ?
169+ . 0 ;
170+
171+ let insertion = outcome
172+ . events
173+ . search_event :: < CheckedContractEvent > ( )
174+ . with_commitment_status ( FlatCommitmentStatus :: Committed )
175+ . with_predicate ( move |e| {
176+ & e. contract == & cfg. addresses . hyperlane . mailbox
177+ && e. ty == mailbox:: InsertedIntoTree :: EVENT_NAME
178+ } )
179+ . take ( )
180+ . one ( )
181+ . event
182+ . data
183+ . deserialize_json :: < mailbox:: InsertedIntoTree > ( ) ?;
133184
134- outcome. outcome . should_succeed ( ) ;
185+ let checkpoint = try_for (
186+ Duration :: from_secs ( 10 ) ,
187+ Duration :: from_millis ( 100 ) ,
188+ || async { get_checkpoint_from_s3 ( location, & insertion. index . to_string ( ) ) . await } ,
189+ )
190+ . await ?;
135191
136- println ! ( "found at height: {}" , outcome. height) ;
192+ let api = MockApi ;
193+
194+ let raw_message = dispatch. encode ( ) ;
195+ let message_id = Hash256 :: from_inner ( api. keccak256 ( & raw_message) ) ;
196+
197+ assert_eq ! ( message_id. inner( ) , checkpoint. message_id. inner( ) ) ;
198+
199+ let merkle_tree_hook_address =
200+ Addr32 :: from_inner ( checkpoint. merkle_tree_hook_address . into_inner ( ) ) ;
201+ let merkle_root = Hash256 :: from_inner ( checkpoint. root . into_inner ( ) ) ;
202+
203+ let multisig_hash = eip191_hash ( multisig_hash (
204+ domain_hash (
205+ dispatch. origin_domain ,
206+ merkle_tree_hook_address,
207+ HYPERLANE_DOMAIN_KEY ,
208+ ) ,
209+ merkle_root,
210+ checkpoint. index ,
211+ Hash256 :: from_inner ( checkpoint. message_id . into_inner ( ) ) ,
212+ ) ) ;
213+
214+ let pk = api. secp256k1_pubkey_recover (
215+ & multisig_hash,
216+ & checkpoint. serialized_signature [ ..64 ] ,
217+ checkpoint. serialized_signature [ 64 ] - 27 , // Ethereum uses recovery IDs 27, 28 instead of 0, 1.
218+ false , // We need the _uncompressed_ public key for deriving address!
219+ ) ?;
220+ let pk_hash = api. keccak256 ( & pk[ 1 ..] ) ;
221+ let address: [ u8 ; 20 ] = pk_hash[ 12 ..] . try_into ( ) . unwrap ( ) ;
222+
223+ assert_eq ! ( Addr :: from_inner( address) , recover_address_should_be) ;
137224
138225 Ok ( ( ) )
139226}
0 commit comments