diff --git a/syllabus/4-Smart_Contracts/6-Wasm_ink!_slides.md b/syllabus/4-Smart_Contracts/6-Wasm_ink!_slides.md index a91a60b38..5be3e89a7 100644 --- a/syllabus/4-Smart_Contracts/6-Wasm_ink!_slides.md +++ b/syllabus/4-Smart_Contracts/6-Wasm_ink!_slides.md @@ -1600,264 +1600,3 @@ impl ChainExtension for HeavyCryptoOutsourcingExtension { ## Chain extension: reaching even further - ---- - -## Testing contracts - ---- - -## Testing contracts - -
- - - ---- - -## Testing contracts - - - ---- - -## Unit tests - -
- -```rust [1-3] -#[ink::test] -fn erc20_transfer_works() { - let mut erc20 = Erc20::new(100); - - assert_eq!(erc20.balance_of(BOB), 0); - // Alice transfers 10 tokens to Bob. - assert_eq!(erc20.transfer(BOB, 10), Ok(())); - // Bob owns 10 tokens. - assert_eq!(erc20.balance_of(BOB), 10); - - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(emitted_events.len(), 2); - - // Check first transfer event related to ERC-20 instantiation. - assert_transfer_event( - &emitted_events[0], None, Some(ALICE), 100, - ); - // Check the second transfer event relating to the actual transfer. - assert_transfer_event( - &emitted_events[1], Some(ALICE), Some(BOB), 10, - ); -} -``` - ---- - -## Unit tests - -
- -```rust [5-9] -#[ink::test] -fn erc20_transfer_works() { - let mut erc20 = Erc20::new(100); - - assert_eq!(erc20.balance_of(BOB), 0); - // Alice transfers 10 tokens to Bob. - assert_eq!(erc20.transfer(BOB, 10), Ok(())); - // Bob owns 10 tokens. - assert_eq!(erc20.balance_of(BOB), 10); - - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(emitted_events.len(), 2); - - // Check first transfer event related to ERC-20 instantiation. - assert_transfer_event( - &emitted_events[0], None, Some(ALICE), 100, - ); - // Check the second transfer event relating to the actual transfer. - assert_transfer_event( - &emitted_events[1], Some(ALICE), Some(BOB), 10, - ); -} -``` - ---- - -## Unit tests - -
- -```rust [11-22] -#[ink::test] -fn erc20_transfer_works() { - let mut erc20 = Erc20::new(100); - - assert_eq!(erc20.balance_of(BOB), 0); - // Alice transfers 10 tokens to Bob. - assert_eq!(erc20.transfer(BOB, 10), Ok(())); - // Bob owns 10 tokens. - assert_eq!(erc20.balance_of(BOB), 10); - - let emitted_events = ink::env::test::recorded_events().collect::>(); - assert_eq!(emitted_events.len(), 2); - - // Check first transfer event related to ERC-20 instantiation. - assert_transfer_event( - &emitted_events[0], None, Some(ALICE), 100, - ); - // Check the second transfer event relating to the actual transfer. - assert_transfer_event( - &emitted_events[1], Some(ALICE), Some(BOB), 10, - ); -} -``` - ---- - -## E2E tests - -
- -```rust [1-7] -#[ink_e2e::test] -async fn e2e_transfer(mut client: ink_e2e::Client) -> E2EResult<()> { - let constructor = Erc20Ref::new(total_supply); - let erc20 = client - .instantiate("erc20", &ink_e2e::alice(), constructor, 0, None) - .await - .expect("instantiate failed"); - - let mut call = erc20.call::(); - let total_supply_msg = call.total_supply(); - let total_supply_res = client - .call_dry_run(&ink_e2e::bob(), &total_supply_msg, 0, None) - .await; - ... -} -``` - ---- - -## E2E tests - -
- -```rust [9-13] -#[ink_e2e::test] -async fn e2e_transfer(mut client: ink_e2e::Client) -> E2EResult<()> { - let constructor = Erc20Ref::new(total_supply); - let erc20 = client - .instantiate("erc20", &ink_e2e::alice(), constructor, 0, None) - .await - .expect("instantiate failed"); - - let mut call = erc20.call::(); - let total_supply_msg = call.total_supply(); - let total_supply_res = client - .call_dry_run(&ink_e2e::bob(), &total_supply_msg, 0, None) - .await; - ... -} -``` - ---- - -## E2E tests - -
- -```rust [14] -#[ink_e2e::test] -async fn e2e_transfer(mut client: ink_e2e::Client) -> E2EResult<()> { - let constructor = Erc20Ref::new(total_supply); - let erc20 = client - .instantiate("erc20", &ink_e2e::alice(), constructor, 0, None) - .await - .expect("instantiate failed"); - - let mut call = erc20.call::(); - let total_supply_msg = call.total_supply(); - let total_supply_res = client - .call_dry_run(&ink_e2e::bob(), &total_supply_msg, 0, None) - .await; - ... -} -``` - ---- - -## E2E pipeline: traps, traps everywhere - -
- -1. Preparing and encoding transaction data (_client side_) -1. Signing the transaction (_client side_) -1. Sending transaction to a node (_client side_) -1. Block and event subscribing (_client side_) -1. Transaction pool processing (_node side_) -1. Block building (_node side_) -1. Block dissemination (_node side_) -1. Import queue processing (_node side_) -1. Block finalizing (_node side_) -1. Block execution (_node side_) -1. Transaction execution (_runtime side_) -1. Event emitting (_node side_) -1. Event capturing (_client side_) -1. Event processing (_client side_) -1. State fetching via RPC calling (_client side_) -1. State report (_node side_) -1. State validation (_client side_) - -
- ---- - -## E2E pipeline: traps, traps everywhere - - - ---- - -## Test core - -
- -1. Preparing and encoding transaction data (_given_) -1. Transaction execution (_when_) -1. State validation (_then_) - ---- - -## quasi-E2E tests - -
- -Interact directly with runtime, skipping node layer. - ---- - -## quasi-E2E tests - -
- -```rust -#[test] -fn flipping() -> Result<(), Box> { - let init_value = Session::::new(transcoder())? - .deploy_and(bytes(), "new", &["true".to_string()], vec![])? - .call_and("flip", &[])? - .call_and("flip", &[])? - .call_and("flip", &[])? - .call_and("get", &[])? - .last_call_return() - .expect("Call was successful"); - - assert_eq!(init_value, ok(Value::Bool(false))); - - Ok(()) -} -``` - ---- - -## Local playing with contracts using `drink-cli` diff --git a/syllabus/4-Smart_Contracts/7-Testing_ink_contracts_slides.md b/syllabus/4-Smart_Contracts/7-Testing_ink_contracts_slides.md new file mode 100644 index 000000000..6e5dc3da2 --- /dev/null +++ b/syllabus/4-Smart_Contracts/7-Testing_ink_contracts_slides.md @@ -0,0 +1,376 @@ +--- +title: Testing ink! Smart Contracts +--- + + + +# Testing ink! Smart Contracts + +--- + +## How to validate contract behavior? + +
+ +- linter/static checks +- common vulnerability patterns +- unit testing +- end-to-end testing +- quasi-end-to-end testing +- fuzzing +- ... + +--- + +## How to validate contract behavior? + +
+ +- linter/static checks +- common vulnerability patterns +- unit testing +- end-to-end testing +- quasi-end-to-end testing +- fuzzing +- ... + +--- + +## Blockchain node onion + +--- + +## Blockchain node onion + +
+ + + +--- + +## Blockchain node onion + + + +- networking +- block production, dissemination, finalization +- storage management +- off-chain maintenance, querying, indexing + +--- + +## Blockchain node onion + + + +- computing new state based on the previous one and a single transaction + +--- + +## Blockchain node onion + + + +- executing contract calls + +--- + +## Blockchain node onion + +
+ + + +--- + +## The stack + + + +--- + +## Case study + +
+ +- an on-chain round-based game +- a rectangular grid to be colored +- in every round a player can color a single cell with their specific color (they choose the coordinates) +- after a fixed number of rounds the game ends and the player with the most cells of their color wins + +--- + +## Case study + +
+ +https://github.com/Polkadot-Blockchain-Academy/ink-testing-strategies/ + +--- + +## Unit tests + +
+ +Just like classic component testing. + +--- + +## Unit tests + +
+ +```rust +#[ink::test] +fn check_dummy_playing() { + let mut player = MyPlayer::new((4, 4), 0); + + for turn in 0..16 { + let (x, y) = player.my_turn().expect("should return coordinates"); + assert_eq!(x, turn % 4); + assert_eq!(y, turn / 4 % 4); + } +} +``` + +--- + +## Unit tests: possibilities + +
+ +```rust [1-3] +#[ink::test] +fn erc20_transfer_works() { + let mut erc20 = Erc20::new(100); + + assert_eq!(erc20.balance_of(BOB), 0); + // Alice transfers 10 tokens to Bob. + assert_eq!(erc20.transfer(BOB, 10), Ok(())); + // Bob owns 10 tokens. + assert_eq!(erc20.balance_of(BOB), 10); + + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 2); + + // Check first transfer event related to ERC-20 instantiation. + assert_transfer_event( + &emitted_events[0], None, Some(ALICE), 100, + ); + // Check the second transfer event relating to the actual transfer. + assert_transfer_event( + &emitted_events[1], Some(ALICE), Some(BOB), 10, + ); +} +``` + +--- + +## Unit tests: possibilities + +
+ +```rust [5-9] +#[ink::test] +fn erc20_transfer_works() { + let mut erc20 = Erc20::new(100); + + assert_eq!(erc20.balance_of(BOB), 0); + // Alice transfers 10 tokens to Bob. + assert_eq!(erc20.transfer(BOB, 10), Ok(())); + // Bob owns 10 tokens. + assert_eq!(erc20.balance_of(BOB), 10); + + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 2); + + // Check first transfer event related to ERC-20 instantiation. + assert_transfer_event( + &emitted_events[0], None, Some(ALICE), 100, + ); + // Check the second transfer event relating to the actual transfer. + assert_transfer_event( + &emitted_events[1], Some(ALICE), Some(BOB), 10, + ); +} +``` + +--- + +## Unit tests: possibilities + +
+ +```rust [11-22] +#[ink::test] +fn erc20_transfer_works() { + let mut erc20 = Erc20::new(100); + + assert_eq!(erc20.balance_of(BOB), 0); + // Alice transfers 10 tokens to Bob. + assert_eq!(erc20.transfer(BOB, 10), Ok(())); + // Bob owns 10 tokens. + assert_eq!(erc20.balance_of(BOB), 10); + + let emitted_events = ink::env::test::recorded_events().collect::>(); + assert_eq!(emitted_events.len(), 2); + + // Check first transfer event related to ERC-20 instantiation. + assert_transfer_event( + &emitted_events[0], None, Some(ALICE), 100, + ); + // Check the second transfer event relating to the actual transfer. + assert_transfer_event( + &emitted_events[1], Some(ALICE), Some(BOB), 10, + ); +} +``` + +--- + +## E2E tests + +
+ +Full flow execution. + +--- + +## E2E pipeline: traps, traps everywhere + +
+ +1. Preparing and encoding transaction data (_client side_) +1. Signing the transaction (_client side_) +1. Sending transaction to a node (_client side_) +1. Block and event subscribing (_client side_) +1. Transaction pool processing (_node side_) +1. Block building (_node side_) +1. Block dissemination (_node side_) +1. Import queue processing (_node side_) +1. Block finalizing (_node side_) +1. Block execution (_node side_) +1. Transaction execution (_runtime side_) +1. Event emitting (_node side_) +1. Event capturing (_client side_) +1. Event processing (_client side_) +1. State fetching via RPC calling (_client side_) +1. State report (_node side_) +1. State validation (_client side_) + +
+ +--- + +## E2E pipeline: traps, traps everywhere + + + +--- + +## E2E pipeline: fear, fear everywhere + + + +--- + +## E2E pipeline: traps, traps everywhere + +
+ +1. **Preparing and encoding transaction data (_client side_)** +1. Signing the transaction (_client side_) +1. Sending transaction to a node (_client side_) +1. Block and event subscribing (_client side_) +1. Transaction pool processing (_node side_) +1. Block building (_node side_) +1. Block dissemination (_node side_) +1. Import queue processing (_node side_) +1. Block finalizing (_node side_) +1. Block execution (_node side_) +1. **Transaction execution (_runtime side_)** +1. Event emitting (_node side_) +1. Event capturing (_client side_) +1. Event processing (_client side_) +1. State fetching via RPC calling (_client side_) +1. State report (_node side_) +1. **State validation (_client side_)** + +
+ +--- + +## Test core + +
+ +1. Preparing and encoding transaction data (**given**) +1. Transaction execution (**when**) +1. State validation (**then**) + +--- + +## quasi-E2E tests + +
+ +Interact directly with runtime, skipping node layer. + + + +--- + +## quasi-E2E tests with `drink!` + +
+ +https://github.com/inkdevhub/drink + + + +--- + +## `drink!` test + +
+ +```rust +#[drink::test] +fn flipping() -> Result<(), Box> { + let contract = BundleProvider::Flipper.bundle()?; + let init_value: bool = Session::::new()? + .deploy_bundle_and(contract, "new", &["true"], NO_SALT, NO_ENDOWMENT)? + .call_and("flip", NO_ARGS, NO_ENDOWMENT)? + .call_and("flip", NO_ARGS, NO_ENDOWMENT)? + .call_and("flip", NO_ARGS, NO_ENDOWMENT)? + .call_and("get", NO_ARGS, NO_ENDOWMENT)? + .record() + .last_call_return_decoded()? + .expect("Call was successful"); + + assert_eq!(init_value, false); + + Ok(()) +} +``` + +--- + +## Local playing with contracts using `drink-cli` + + + +--- + +## Takeaways + +
+ +- **Always test your contracts!** +- Unit tests in the initial dev phase and for complex methods. +- drink! during development and for long-running simulations. +- E2E suite for the final product. + + diff --git a/syllabus/4-Smart_Contracts/img/ink/ink-testing-blind.png b/syllabus/4-Smart_Contracts/img/ink/ink-testing-blind.png new file mode 100644 index 000000000..b47c7e002 Binary files /dev/null and b/syllabus/4-Smart_Contracts/img/ink/ink-testing-blind.png differ diff --git a/syllabus/4-Smart_Contracts/img/ink/ink-testing-blockchain-is-an-onion.png b/syllabus/4-Smart_Contracts/img/ink/ink-testing-blockchain-is-an-onion.png new file mode 100644 index 000000000..6b56ce957 Binary files /dev/null and b/syllabus/4-Smart_Contracts/img/ink/ink-testing-blockchain-is-an-onion.png differ diff --git a/syllabus/4-Smart_Contracts/img/ink/ink-testing-drink-cli.mp4 b/syllabus/4-Smart_Contracts/img/ink/ink-testing-drink-cli.mp4 new file mode 100644 index 000000000..6204e28b1 Binary files /dev/null and b/syllabus/4-Smart_Contracts/img/ink/ink-testing-drink-cli.mp4 differ diff --git a/syllabus/4-Smart_Contracts/img/ink/ink-testing-drink-potion.png b/syllabus/4-Smart_Contracts/img/ink/ink-testing-drink-potion.png new file mode 100644 index 000000000..b1a53ddcf Binary files /dev/null and b/syllabus/4-Smart_Contracts/img/ink/ink-testing-drink-potion.png differ diff --git a/syllabus/4-Smart_Contracts/img/ink/ink-testing-fear.png b/syllabus/4-Smart_Contracts/img/ink/ink-testing-fear.png new file mode 100644 index 000000000..6d054d2d3 Binary files /dev/null and b/syllabus/4-Smart_Contracts/img/ink/ink-testing-fear.png differ diff --git a/syllabus/4-Smart_Contracts/img/ink/ink-testing-takeaway.png b/syllabus/4-Smart_Contracts/img/ink/ink-testing-takeaway.png new file mode 100644 index 000000000..7bec054b9 Binary files /dev/null and b/syllabus/4-Smart_Contracts/img/ink/ink-testing-takeaway.png differ diff --git a/syllabus/4-Smart_Contracts/img/ink/trap.gif b/syllabus/4-Smart_Contracts/img/ink/ink-testing-trap.gif similarity index 100% rename from syllabus/4-Smart_Contracts/img/ink/trap.gif rename to syllabus/4-Smart_Contracts/img/ink/ink-testing-trap.gif diff --git a/syllabus/4-Smart_Contracts/img/ink/onions.png b/syllabus/4-Smart_Contracts/img/ink/onions.png deleted file mode 100644 index 1a935e1c4..000000000 Binary files a/syllabus/4-Smart_Contracts/img/ink/onions.png and /dev/null differ