diff --git a/ethexe/cli/src/tests.rs b/ethexe/cli/src/tests.rs index 757b5d39646..a15a9f1fda9 100644 --- a/ethexe/cli/src/tests.rs +++ b/ethexe/cli/src/tests.rs @@ -322,6 +322,108 @@ async fn mailbox() { assert!(schedule.is_empty(), "{:?}", schedule); } +#[tokio::test(flavor = "multi_thread")] +#[ntest::timeout(60_000)] +async fn incoming_transfers() { + gear_utils::init_default_logger(); + + let mut env = TestEnv::new(Default::default()).await.unwrap(); + + let sequencer_public_key = env.wallets.next(); + let mut node = env.new_node( + NodeConfig::default() + .sequencer(sequencer_public_key) + .validator(env.validators[0]), + ); + node.start_service().await; + + let res = env + .upload_code(demo_ping::WASM_BINARY) + .await + .unwrap() + .wait_for() + .await + .unwrap(); + + let code_id = res.code_id; + + let res = env + .create_program(code_id, b"PING", 0) + .await + .unwrap() + .wait_for() + .await + .unwrap(); + + let ping_id = res.program_id; + + let wvara = env.ethereum.router().wvara(); + let ping = env.ethereum.mirror(ping_id.to_address_lossy().into()); + + let on_eth_balance = wvara + .query() + .balance_of(ping.address().0.into()) + .await + .unwrap(); + assert_eq!(on_eth_balance, 0); + + let state_hash = ping.query().state_hash().await.unwrap(); + let local_balance = node.db.read_state(state_hash).unwrap().balance; + assert_eq!(local_balance, 0); + + // 1_000 tokens + const VALUE_SENT: u128 = 1_000_000_000_000_000; + + let mut listener = env.events_publisher().subscribe().await; + + env.transfer_wvara(ping_id, VALUE_SENT).await; + + listener + .apply_until_block_event(|e| { + Ok(matches!(e, BlockEvent::Router(RouterEvent::BlockCommitted { .. })).then_some(())) + }) + .await + .unwrap(); + + let on_eth_balance = wvara + .query() + .balance_of(ping.address().0.into()) + .await + .unwrap(); + assert_eq!(on_eth_balance, VALUE_SENT); + + let state_hash = ping.query().state_hash().await.unwrap(); + let local_balance = node.db.read_state(state_hash).unwrap().balance; + assert_eq!(local_balance, VALUE_SENT); + + env.approve_wvara(ping_id).await; + + let res = env + .send_message(ping_id, b"PING", VALUE_SENT) + .await + .unwrap() + .wait_for() + .await + .unwrap(); + + assert_eq!( + res.reply_code, + ReplyCode::Success(SuccessReplyReason::Manual) + ); + assert_eq!(res.reply_value, 0); + + let on_eth_balance = wvara + .query() + .balance_of(ping.address().0.into()) + .await + .unwrap(); + assert_eq!(on_eth_balance, 2 * VALUE_SENT); + + let state_hash = ping.query().state_hash().await.unwrap(); + let local_balance = node.db.read_state(state_hash).unwrap().balance; + assert_eq!(local_balance, 2 * VALUE_SENT); +} + #[tokio::test(flavor = "multi_thread")] #[ntest::timeout(120_000)] async fn ping_reorg() { @@ -934,6 +1036,17 @@ mod utils { wvara.approve_all(program_address.0.into()).await.unwrap(); } + pub async fn transfer_wvara(&self, program_id: ActorId, value: u128) { + log::info!("📗 Transferring {value} WVara to {program_id}"); + + let program_address = ethexe_signer::Address::try_from(program_id).unwrap(); + let wvara = self.ethereum.router().wvara(); + wvara + .transfer(program_address.0.into(), value) + .await + .unwrap(); + } + pub fn events_publisher(&self) -> EventsPublisher { EventsPublisher { broadcaster: self.broadcaster.clone(), diff --git a/ethexe/processor/src/handling/events.rs b/ethexe/processor/src/handling/events.rs index dfa93ad2be1..ed44a3a295d 100644 --- a/ethexe/processor/src/handling/events.rs +++ b/ethexe/processor/src/handling/events.rs @@ -151,12 +151,24 @@ impl Processor { pub(crate) fn handle_wvara_event( &mut self, - _in_block_transitions: &mut InBlockTransitions, + in_block_transitions: &mut InBlockTransitions, event: WVaraEvent, ) -> Result<()> { match event { - WVaraEvent::Transfer { .. } => { - log::debug!("Handler not yet implemented: {event:?}"); + WVaraEvent::Transfer { from, to, value } => { + if let Some(state_hash) = in_block_transitions.state_of(&to) { + if in_block_transitions.state_of(&from).is_none() { + let new_state_hash = self.db.mutate_state(state_hash, |_, state| { + state.balance += value; + Ok(()) + })?; + + in_block_transitions + .modify_state(to, new_state_hash) + .expect("queried above so infallible here"); + } + } + Ok(()) } } diff --git a/ethexe/runtime/common/src/journal.rs b/ethexe/runtime/common/src/journal.rs index 253ba7604e9..cb80ad504a8 100644 --- a/ethexe/runtime/common/src/journal.rs +++ b/ethexe/runtime/common/src/journal.rs @@ -374,7 +374,21 @@ impl JournalHandler for Handler<'_, S> { } fn send_value(&mut self, from: ProgramId, to: Option, value: u128) { - // TODO: implement + // TODO (breathx): implement rest of cases. + if let Some(to) = to { + if self.in_block_transitions.state_of(&from).is_some() { + return; + } + + let state_hash = self.update_state(to, |state| { + state.balance += value; + Ok(()) + }); + + self.in_block_transitions + .modify_state_with(to, state_hash, value, vec![], vec![]) + .expect("queried above; infallible"); + } } fn store_new_programs( diff --git a/ethexe/signer/src/lib.rs b/ethexe/signer/src/lib.rs index c7ab71c26e8..58afaebc779 100644 --- a/ethexe/signer/src/lib.rs +++ b/ethexe/signer/src/lib.rs @@ -26,7 +26,7 @@ pub use sha3; pub use signature::Signature; use anyhow::{anyhow, Result}; -use gprimitives::ActorId; +use gprimitives::{ActorId, H160}; use parity_scale_codec::{Decode, Encode}; use sha3::Digest as _; use signature::RawSignature; @@ -51,6 +51,18 @@ impl From for PublicKey { #[derive(Encode, Decode, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Address(pub [u8; 20]); +impl From<[u8; 20]> for Address { + fn from(value: [u8; 20]) -> Self { + Self(value) + } +} + +impl From for Address { + fn from(value: H160) -> Self { + Self(value.into()) + } +} + impl TryFrom for Address { type Error = anyhow::Error;