-
Notifications
You must be signed in to change notification settings - Fork 480
Introduces contract demonstrating unit + e2e test event decoding. #1738
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c871d21
796981a
74ea83e
8b2de16
91a196c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| [package] | ||
| name = "testing_event_decode" | ||
| version = "4.1.0" | ||
| authors = ["Parity Technologies <[email protected]>"] | ||
| edition = "2021" | ||
| publish = false | ||
|
|
||
| [dependencies] | ||
| ink = { path = "../../crates/ink", default-features = false } | ||
|
|
||
| scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] } | ||
| scale-info = { version = "2.5", default-features = false, features = ["derive"], optional = true } | ||
|
|
||
| [dev-dependencies] | ||
| ink_e2e = { path = "../../crates/e2e" } | ||
|
|
||
| [lib] | ||
| path = "lib.rs" | ||
|
|
||
| [features] | ||
| default = ["std"] | ||
| std = [ | ||
| "ink/std", | ||
| "scale/std", | ||
| "scale-info/std", | ||
| ] | ||
| ink-as-dependency = [] | ||
| e2e-tests = [] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # Testing Events Reference Implementation | ||
|
|
||
| Capturing events for ink! end-to-end tests in particular is not clearly documented, nor are there clear reference implementations. This contract contains a minimalist flipflop contract that emits two different events when called. It contains both unit and e2e test modules illustrating one way to check for multiple events in both testing environments. | ||
|
|
||
| The end-to-end event test/check is awkward and could certainly be improved on. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please create a follow-up issue for this? |
||
|
|
||
| Notes: | ||
|
|
||
| - e2e: depending on the field number and content of an event struct, the byte range in the `&event` that is decoded may be either `&event[34..]` or `&event[35..]`. This is a point to improve on, dealing with the prepending bytes that don't pertain to the field values. | ||
|
|
||
| - There is probably a way to filter for event types simultaneously, instead of one at a time (that is, keeping track of which events were caught by using a boolean and assert). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,241 @@ | ||
| //! This is a reference implementation with one approach to decoding | ||
| //! (capturing emitted) events within unit and E2E tests. | ||
|
|
||
| #![cfg_attr(not(feature = "std"), no_std)] | ||
|
|
||
| #[ink::contract] | ||
| mod testing_event_decode { | ||
|
|
||
| #[ink(storage)] | ||
| pub struct TestingEventDecode {} | ||
|
|
||
| #[ink(event)] | ||
| pub struct FlipEvent { | ||
| #[ink(topic)] | ||
| flipper: AccountId, | ||
| #[ink(topic)] | ||
| value: bool, | ||
| } | ||
|
|
||
| #[ink(event)] | ||
| pub struct FlopEvent { | ||
| #[ink(topic)] | ||
| flipper: AccountId, | ||
| #[ink(topic)] | ||
| flopper: AccountId, | ||
| #[ink(topic)] | ||
| value: bool, | ||
| } | ||
|
|
||
| impl Default for TestingEventDecode { | ||
| fn default() -> Self { | ||
| Self::new() | ||
| } | ||
| } | ||
|
|
||
| impl TestingEventDecode { | ||
|
|
||
| #[ink(constructor)] | ||
| pub fn new() -> Self { | ||
| Self {} | ||
| } | ||
|
|
||
| /// This message emits two events for test verification. | ||
| #[ink(message)] | ||
| pub fn flipflop(&mut self) { | ||
| let caller = self.env().caller(); | ||
| let contract = self.env().account_id(); | ||
|
|
||
| // Emit FlipEvent. | ||
| self.env().emit_event(FlipEvent { | ||
| flipper: caller, | ||
| value: true, | ||
| }); | ||
|
|
||
| // Emit FlopEvent. | ||
| self.env().emit_event(FlopEvent { | ||
| flipper: caller, | ||
| flopper: contract, | ||
| value: false, | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /// This is one way to capture and check event emission in ink unit tests. | ||
| #[cfg(test)] | ||
| mod tests { | ||
|
|
||
| use super::*; | ||
| use ink::env::test::EmittedEvent; | ||
|
|
||
| type Event = <TestingEventDecode as ::ink::reflect::ContractEventBase>::Type; | ||
|
|
||
| fn decode_events(emitted_events: Vec<EmittedEvent>) -> Vec<Event> { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is actually a very useful function to be added to e2e create directly. Can you please look into this?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To clarify, do you mean reimplement this If the former, this would be part of the followup issue regarding improving event decode to not be so awkward. In any case, this might be something @ganesh1233456 or @alessandro-baldassarre may wish to address individually. I will see to it that the proper issues are created.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think for unit tests, we want to have something like With e2e tests it's a little bit trickier, we probably want to have something like |
||
| emitted_events | ||
| .into_iter() | ||
| .map(|event| { | ||
| <Event as scale::Decode>::decode(&mut &event.data[..]) | ||
| .expect("Invalid event data") | ||
| }) | ||
| .collect() | ||
| } | ||
|
|
||
| #[ink::test] | ||
| fn unittest_event_emission_capture_decode() { | ||
| let accounts = | ||
| ink::env::test::default_accounts::<ink::env::DefaultEnvironment>(); | ||
| ink::env::test::set_caller::<ink::env::DefaultEnvironment>(accounts.bob); | ||
|
|
||
| let mut contract = TestingEventDecode::new(); | ||
| contract.flipflop(); | ||
|
|
||
| // Decode event. | ||
| let emitted_events = ink::env::test::recorded_events().collect::<Vec<_>>(); | ||
| let decoded_events = decode_events(emitted_events); | ||
|
|
||
| let mut gotflip = false; | ||
| let mut gotflop = false; | ||
|
|
||
| // Check events were emitted by the `flipflop` function. | ||
| for event in &decoded_events { | ||
| match event { | ||
| Event::FlipEvent(FlipEvent { flipper, value }) => { | ||
| assert!(*value, "unexpected FlipEvent.value"); | ||
| assert_eq!( | ||
| *flipper, accounts.bob, | ||
| "unexpected FlipEvent.flipper" | ||
| ); | ||
| gotflip = true; | ||
| } | ||
| Event::FlopEvent(FlopEvent { | ||
| flipper, | ||
| flopper, | ||
| value, | ||
| }) => { | ||
| assert!(!*value, "unexpected FlopEvent.value"); | ||
| assert_eq!( | ||
| *flipper, accounts.bob, | ||
| "unexpected FlopEvent.flipper" | ||
| ); | ||
| assert_eq!( | ||
| *flopper, accounts.alice, | ||
| "unexpected FlopEvent.flopper" | ||
| ); | ||
| gotflop = true; | ||
| } | ||
| }; | ||
| } | ||
| assert!(gotflip, "expected flip event not captured"); | ||
| assert!(gotflop, "expected flop event not captured"); | ||
| } | ||
| } | ||
|
|
||
| /// This is one way to capture and check event emission in ink E2E tests. | ||
| #[cfg(all(test, feature = "e2e-tests"))] | ||
| mod e2e_tests { | ||
|
|
||
| use super::*; | ||
| use ink_e2e::build_message; | ||
|
|
||
| type E2EResult<T> = std::result::Result<T, Box<dyn std::error::Error>>; | ||
|
|
||
| /// Verify that we can capture emitted events and decode to check their fields. | ||
| #[ink_e2e::test] | ||
| async fn e2etest_event_emission_capture_decode( | ||
| mut client: ink_e2e::Client<C, E>, | ||
| ) -> E2EResult<()> { | ||
| let bob_account = ink_e2e::account_id(ink_e2e::AccountKeyring::Bob); | ||
| let constructor = TestingEventDecodeRef::new(); | ||
| let contract_account_id = client | ||
| .instantiate( | ||
| "testing_event_decode", | ||
| &ink_e2e::bob(), | ||
| constructor, | ||
| 0, | ||
| None, | ||
| ) | ||
| .await | ||
| .expect("instantiate failed") | ||
| .account_id; | ||
|
|
||
| // Check that FlipEvent was successfully emitted. | ||
| let flipflop_msg = | ||
| build_message::<TestingEventDecodeRef>(contract_account_id) | ||
| .call(|contract| contract.flipflop()); | ||
| let flipflop_result = client | ||
| .call(&ink_e2e::bob(), flipflop_msg, 0, None) | ||
| .await | ||
| .expect("flipflop failed"); | ||
|
|
||
| // Filter the events. | ||
| let contract_emitted_event = flipflop_result | ||
| .events | ||
| .iter() | ||
| .find(|event| { | ||
| event.as_ref().expect("bad event").event_metadata().event() | ||
| == "ContractEmitted" | ||
| && String::from_utf8_lossy( | ||
| event.as_ref().expect("bad event").bytes(), | ||
| ) | ||
| .to_string() | ||
| .contains("TestingEventDecode::FlipEvent") | ||
| }) | ||
| .expect("Expected flip event") | ||
| .unwrap(); | ||
|
|
||
| // Decode the expected event type. | ||
| let event = contract_emitted_event.field_bytes(); | ||
| let decode_event = <FlipEvent as scale::Decode>::decode(&mut &event[34..]) | ||
| .expect("invalid data"); | ||
|
|
||
| let FlipEvent { flipper, value } = decode_event; | ||
|
|
||
| // Check that FlopEvent was successully emitted. | ||
| assert!(value, "unexpected FlipEvent.value"); | ||
| assert_eq!(flipper, bob_account, "unexpected FlipEvent.flipper"); | ||
|
|
||
| // Build flipflop message. | ||
| let flipflop_msg = | ||
| build_message::<TestingEventDecodeRef>(contract_account_id) | ||
| .call(|contract| contract.flipflop()); | ||
| let flipflop_result = client | ||
| .call(&ink_e2e::bob(), flipflop_msg, 0, None) | ||
| .await | ||
| .expect("flipflop failed"); | ||
|
|
||
| // Filter the events. | ||
| let contract_emitted_event = flipflop_result | ||
| .events | ||
| .iter() | ||
| .find(|event| { | ||
| event.as_ref().expect("bad event").event_metadata().event() | ||
| == "ContractEmitted" | ||
| && String::from_utf8_lossy( | ||
| event.as_ref().expect("bad event").bytes(), | ||
| ) | ||
| .to_string() | ||
| .contains("TestingEventDecode::FlopEvent") | ||
| }) | ||
| .expect("Expected flop event") | ||
| .unwrap(); | ||
|
|
||
| // Decode the expected event type. | ||
| let event = contract_emitted_event.field_bytes(); | ||
| let decode_event = <FlopEvent as scale::Decode>::decode(&mut &event[35..]) | ||
| .expect("invalid data"); | ||
|
|
||
| let FlopEvent { | ||
| flipper, | ||
| flopper, | ||
| value, | ||
| } = decode_event; | ||
|
|
||
| // Check event emitted by the `flip` function. | ||
| assert!(!value, "unexpected FlopEvent.value"); | ||
| assert_eq!(flipper, bob_account, "unexpected FlopEvent.flipper"); | ||
| assert_eq!(flopper, contract_account_id, "unexpected FlopEvent.flopper"); | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This contract contains a minimalist flipper contract that emits two different events when called. It contains both unit and e2e test modules illustrating one way to check for multiple events in both testing environments.