diff --git a/examples/fungible/tests/cross_chain.rs b/examples/fungible/tests/cross_chain.rs index 5071b29e1dd..ec8ce26cb39 100644 --- a/examples/fungible/tests/cross_chain.rs +++ b/examples/fungible/tests/cross_chain.rs @@ -67,3 +67,76 @@ async fn test_cross_chain_transfer() { Some(transfer_amount), ); } + +/// Test bouncing some tokens back to the sender. +/// +/// Creates the application on a `sender_chain`, initializing it with a single account with some +/// tokens for that chain's owner. Attempts to transfer some tokens to a new `receiver_chain`, +/// but makes the `receiver_chain` reject the transfer message, causing the tokens to be +/// returned back to the sender. +#[tokio::test] +async fn test_bouncing_tokens() { + let initial_amount = Amount::from_tokens(19); + let transfer_amount = Amount::from_tokens(7); + + let (validator, bytecode_id) = TestValidator::with_current_bytecode().await; + let mut sender_chain = validator.new_chain().await; + let sender_account = AccountOwner::from(sender_chain.public_key()); + + let initial_state = InitialStateBuilder::default().with_account(sender_account, initial_amount); + let params = Parameters::new("RET"); + let application_id = sender_chain + .create_application::( + bytecode_id, + params, + initial_state.build(), + vec![], + ) + .await; + + let receiver_chain = validator.new_chain().await; + let receiver_account = AccountOwner::from(receiver_chain.public_key()); + + let messages = sender_chain + .add_block(|block| { + block.with_operation( + application_id, + Operation::Transfer { + owner: sender_account, + amount: transfer_amount, + target_account: Account { + chain_id: receiver_chain.id(), + owner: receiver_account, + }, + }, + ); + }) + .await; + + assert_eq!( + fungible::query_account(application_id, &sender_chain, sender_account).await, + Some(initial_amount.saturating_sub(transfer_amount)), + ); + + assert_eq!(messages.len(), 2); + + receiver_chain + .add_block(move |block| { + block + .with_incoming_message(messages[0]) + .with_message_rejection(messages[1]); + }) + .await; + + assert_eq!( + fungible::query_account(application_id, &receiver_chain, receiver_account).await, + None, + ); + + sender_chain.handle_received_messages().await; + + assert_eq!( + fungible::query_account(application_id, &sender_chain, sender_account).await, + Some(initial_amount), + ); +} diff --git a/linera-sdk/src/test/integration/block.rs b/linera-sdk/src/test/integration/block.rs index 5ec6ce08a59..347668ab853 100644 --- a/linera-sdk/src/test/integration/block.rs +++ b/linera-sdk/src/test/integration/block.rs @@ -12,7 +12,7 @@ use linera_base::{ identifiers::{ApplicationId, ChainId, MessageId, Owner}, }; use linera_chain::data_types::{ - Block, Certificate, HashedValue, IncomingMessage, LiteVote, SignatureAggregator, + Block, Certificate, HashedValue, IncomingMessage, LiteVote, MessageAction, SignatureAggregator, }; use linera_execution::{system::SystemOperation, Operation}; @@ -23,7 +23,7 @@ use crate::ToBcsBytes; /// [`Certificate`]s using a [`TestValidator`]. pub struct BlockBuilder { block: Block, - incoming_messages: Vec, + incoming_messages: Vec<(MessageId, MessageAction)>, validator: TestValidator, } @@ -126,7 +126,18 @@ impl BlockBuilder { /// The block that produces the message must have already been executed by the test validator, /// so that the message is already in the inbox of the microchain this block belongs to. pub fn with_incoming_message(&mut self, message_id: MessageId) -> &mut Self { - self.incoming_messages.push(message_id); + self.incoming_messages + .push((message_id, MessageAction::Accept)); + self + } + + /// Rejects an incoming message referenced by the [`MessageId`]. + /// + /// The block that produces the message must have already been executed by the test validator, + /// so that the message is already in the inbox of the microchain this block belongs to. + pub fn with_message_rejection(&mut self, message_id: MessageId) -> &mut Self { + self.incoming_messages + .push((message_id, MessageAction::Reject)); self } @@ -138,7 +149,11 @@ impl BlockBuilder { &mut self, message_ids: impl IntoIterator, ) -> &mut Self { - self.incoming_messages.extend(message_ids); + self.incoming_messages.extend( + message_ids + .into_iter() + .map(|message_id| (message_id, MessageAction::Accept)), + ); self } @@ -193,8 +208,8 @@ impl BlockBuilder { async fn collect_incoming_messages(&mut self) { let chain_id = self.block.chain_id; - for message_id in mem::take(&mut self.incoming_messages) { - let message = self + for (message_id, action) in mem::take(&mut self.incoming_messages) { + let mut message = self .validator .worker() .await @@ -203,6 +218,8 @@ impl BlockBuilder { .expect("Failed to find message to receive in block") .expect("Message that block should consume has not been emitted"); + message.action = action; + self.block.incoming_messages.push(message); } }