-
Notifications
You must be signed in to change notification settings - Fork 482
test refactored #2746
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
base: master
Are you sure you want to change the base?
test refactored #2746
Changes from all commits
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,81 @@ | ||
| # ink! Smart Contract Examples & Integration Tests | ||
|
|
||
| Welcome to the collection of example smart contracts and integration tests for [ink!](https://github.com/use-ink/ink), the relentless smart contract language for Polkadot and Substrate. | ||
|
|
||
| This directory contains a curated set of examples demonstrates everything from basic "Hello World" contracts to advanced upgradeability patterns and cross-chain messaging (XCM). | ||
|
|
||
| ## Directory Structure | ||
|
|
||
| The examples are organized into categories to help you navigate from foundational concepts to complex implementations. | ||
|
|
||
| ### Basics | ||
| *Fundamental building blocks for learning ink!.* | ||
|
|
||
| - **[`flipper`](basics/flipper)**: The classic "Hello World" of smart contracts. A simple boolean value you can flip. | ||
| - **[`incrementer`](basics/incrementer)**: Demonstrates simple state mutation with integers. | ||
| - **[`dns`](basics/dns)**: Shows how to use the storage `Mapping` type. | ||
| - **[`events`](basics/events)**: Examples of defining and emitting events. | ||
| - **[`terminator`](basics/terminator)**: How to remove a contract from storage using `terminate`. | ||
|
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 we put this under the folder |
||
|
|
||
| ### 🪙 Tokens | ||
| *Implementations of standard token interfaces.* | ||
|
|
||
| - **[`erc20`](tokens/erc20)**: Complete implementation of the ERC-20 fungible token standard. | ||
| - **[`erc721`](tokens/erc721)**: Non-Fungible Token (NFT) standard implementation. | ||
| - **[`erc1155`](tokens/erc1155)**: Multi-Token standard implementation. | ||
|
|
||
| ### Traits | ||
| *Defining shared behavior using Rust traits.* | ||
|
|
||
| - **[`erc20`](traits/erc20)**: ERC-20 implemented using ink! trait definitions. | ||
| - **[`flipper`](traits/flipper)**: The flipper contract, but defined via a trait. | ||
| - **[`incrementer`](traits/incrementer)**: Trait-based incrementer. | ||
| - **[`dyn-cross-contract`](traits/dyn-cross-contract)**: How to make cross-contract calls using dynamic trait dispatch. | ||
|
|
||
| ### Cross-Contract Interactions | ||
| *Calling other contracts from your contract.* | ||
|
|
||
| - **[`basic`](cross-contract/basic)**: Simple example of one contract calling another. | ||
| - **[`advanced`](cross-contract/advanced)**: Using call builders to set gas limits and storage deposits. | ||
| - **[`multi-caller`](cross-contract/multi-caller)**: A contract that can switch between calling different target contracts. | ||
| - **[`transfer`](cross-contract/transfer)**: Sending value (tokens) to another contract. | ||
| - **[`invocation`](cross-contract/invocation)**: Instantiating new contracts from within a contract. | ||
|
|
||
| ### Storage | ||
| *Advanced storage layouts and data structures.* | ||
|
|
||
| - **[`basic`](storage/basic)**: Overview of available storage types. | ||
| - **[`complex`](storage/complex)**: Nested structs and Enums in storage. | ||
| - **[`lazyvec`](storage/lazyvec)**: Using `Lazy` vector types for gas optimization. | ||
| - **[`allocator`](storage/allocator)**: Using a custom heap allocator (e.g., `bumpalo`). | ||
|
|
||
| ### Runtime & Chain | ||
|
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.
|
||
| *Interacting with the Substrate chain and runtime.* | ||
|
|
||
| - **[`call-contract`](runtime/call-contract)**: The runtime calling into a contract. | ||
| - **[`e2e-call`](runtime/e2e-call)**: A contract calling a runtime dispatchable (pallet function). | ||
| - **[`xcm`](runtime/xcm)**: Cross-Consensus Messaging examples. | ||
| - **[`precompile`](runtime/precompile)**: interacting with runtime precompiles. | ||
|
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. The |
||
|
|
||
| ### Advanced | ||
| *Complex patterns and niche features.* | ||
|
|
||
| - **[`upgradeable`](advanced/upgradeable)**: Contracts that can upgrade their own code (`set_code_hash`). | ||
|
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 should be in the folder operations |
||
| - **[`custom-env`](advanced/custom-env)**: Defining custom chain extensions and environment types. | ||
| - **[`fuzzing`](advanced/fuzzing)**: Setup for property-based fuzz testing. | ||
| - **[`debugging`](advanced/debugging)**: Techniques for interpreting debug buffers. | ||
|
Comment on lines
+65
to
+66
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 can be in the folder |
||
|
|
||
| ## Running the Tests | ||
|
|
||
| Most examples in this directory are standard Rust crates. You can run their tests using Cargo. | ||
|
|
||
| ```bash | ||
| cd basics/flipper | ||
| cargo test | ||
| ``` | ||
|
|
||
| For End-to-End (E2E) tests that require a running Substrate node (like `substrate-contracts-node`), look for `e2e_tests.rs` files and ensure your environment is set up correctly with the `PINK_Runtime` or a local node. | ||
|
|
||
| ## Contributing | ||
|
|
||
| If you are adding a new example, please place it in the most appropriate category directory. Ensure it includes a `README.md` and basic tests. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| use super::Flip; | ||
| use super::conditional_compilation::ConditionalCompilation; | ||
|
|
||
| #[ink::test] | ||
| fn default_works() { | ||
| let flipper = ConditionalCompilation::new(); | ||
| assert!(!flipper.get()); | ||
| } | ||
|
|
||
| #[ink::test] | ||
| fn it_works() { | ||
| let mut flipper = ConditionalCompilation::new(); | ||
| // Can call using universal call syntax using the trait. | ||
| assert!(!<ConditionalCompilation as Flip>::get(&flipper)); | ||
| <ConditionalCompilation as Flip>::flip(&mut flipper); | ||
| // Normal call syntax possible to as long as the trait is in scope. | ||
| assert!(flipper.get()); | ||
| } | ||
|
|
||
| #[cfg(feature = "foo")] | ||
| #[ink::test] | ||
| fn foo_works() { | ||
| let mut flipper = ConditionalCompilation::new_foo(false); | ||
|
|
||
| flipper.inherent_flip_foo(); | ||
| assert!(flipper.get()); | ||
|
|
||
| <ConditionalCompilation as Flip>::push_foo(&mut flipper, false); | ||
| assert!(!flipper.get()) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| #![cfg_attr(not(feature = "std"), no_std, no_main)] | ||
|
|
||
| use ink::env::{ | ||
| DefaultEnvironment, | ||
| Environment, | ||
| }; | ||
|
|
||
| /// Our custom environment diverges from the `DefaultEnvironment` in the event topics | ||
| /// limit. | ||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||
| #[ink::scale_derive(TypeInfo)] | ||
| pub enum EnvironmentWithManyTopics {} | ||
|
|
||
| impl Environment for EnvironmentWithManyTopics { | ||
| const NATIVE_TO_ETH_RATIO: u32 = | ||
| <DefaultEnvironment as Environment>::NATIVE_TO_ETH_RATIO; | ||
| const TRUST_BACKED_ASSETS_PRECOMPILE_INDEX: u16 = | ||
| <DefaultEnvironment as Environment>::TRUST_BACKED_ASSETS_PRECOMPILE_INDEX; | ||
| const POOL_ASSETS_PRECOMPILE_INDEX: u16 = | ||
| <DefaultEnvironment as Environment>::POOL_ASSETS_PRECOMPILE_INDEX; | ||
|
|
||
| type AccountId = <DefaultEnvironment as Environment>::AccountId; | ||
| type Balance = <DefaultEnvironment as Environment>::Balance; | ||
| type Hash = <DefaultEnvironment as Environment>::Hash; | ||
| type BlockNumber = <DefaultEnvironment as Environment>::BlockNumber; | ||
| type Timestamp = <DefaultEnvironment as Environment>::Timestamp; | ||
| type EventRecord = <DefaultEnvironment as Environment>::EventRecord; | ||
| } | ||
|
|
||
| #[ink::contract(env = crate::EnvironmentWithManyTopics)] | ||
| mod runtime_call { | ||
| /// Trivial contract with a single message that emits an event with many topics. | ||
| #[ink(storage)] | ||
| #[derive(Default)] | ||
| pub struct Topics; | ||
|
|
||
| /// An event that would be forbidden in the default environment, but is completely | ||
| /// valid in our custom one. | ||
| #[ink(event)] | ||
| #[derive(Default)] | ||
| pub struct EventWithTopics { | ||
| #[ink(topic)] | ||
| first_topic: Balance, | ||
| #[ink(topic)] | ||
| second_topic: Balance, | ||
| } | ||
|
|
||
| impl Topics { | ||
| #[ink(constructor)] | ||
| pub fn new() -> Self { | ||
| Default::default() | ||
| } | ||
|
|
||
| /// Emit an event with many topics. | ||
| #[ink(message)] | ||
| pub fn trigger(&mut self) { | ||
| self.env().emit_event(EventWithTopics::default()); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| use super::runtime_call::{EventWithTopics, Topics, TopicsRef}; | ||
| use super::EnvironmentWithManyTopics; | ||
|
|
||
| #[ink::test] | ||
| fn emits_event_with_many_topics() { | ||
| let mut contract = Topics::new(); | ||
| contract.trigger(); | ||
|
|
||
| let emitted_events = ink::env::test::recorded_events(); | ||
| assert_eq!(emitted_events.len(), 1); | ||
|
|
||
| let emitted_event = <EventWithTopics as ink::scale::Decode>::decode( | ||
| &mut &emitted_events[0].data[..], | ||
| ); | ||
|
|
||
| assert!(emitted_event.is_ok()); | ||
| } | ||
|
|
||
| #[cfg(all(test, feature = "e2e-tests"))] | ||
| mod e2e_tests { | ||
| use super::*; | ||
| use ink_e2e::ContractsBackend; | ||
|
|
||
| type E2EResult<T> = Result<T, Box<dyn std::error::Error>>; | ||
|
|
||
| #[cfg(feature = "permissive-node")] | ||
| #[ink_e2e::test(environment = crate::EnvironmentWithManyTopics)] | ||
| async fn calling_custom_environment_works(mut client: Client) -> E2EResult<()> { | ||
| // given | ||
| let mut constructor = TopicsRef::new(); | ||
| let contract = client | ||
| .instantiate("custom-environment", &ink_e2e::alice(), &mut constructor) | ||
| .submit() | ||
| .await | ||
| .expect("instantiate failed"); | ||
| let mut call_builder = contract.call_builder::<Topics>(); | ||
|
|
||
| // when | ||
| let message = call_builder.trigger(); | ||
|
|
||
| let call_res = client | ||
| .call(&ink_e2e::alice(), &message) | ||
| .submit() | ||
| .await | ||
| .expect("call failed"); | ||
|
|
||
| // then | ||
| assert!(call_res.contains_event("Revive", "ContractEmitted")); | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| #[cfg(not(feature = "permissive-node"))] | ||
| #[ink_e2e::test(environment = crate::EnvironmentWithManyTopics)] | ||
| async fn calling_custom_environment_fails_if_incompatible_with_node( | ||
| mut client: Client, | ||
| ) -> E2EResult<()> { | ||
| // given | ||
| let mut constructor = TopicsRef::new(); | ||
| let contract = client | ||
| .instantiate("custom-environment", &ink_e2e::alice(), &mut constructor) | ||
| .submit() | ||
| .await | ||
| .expect("instantiate failed"); | ||
| let mut call_builder = contract.call_builder::<Topics>(); | ||
|
|
||
| let message = call_builder.trigger(); | ||
|
|
||
| // when | ||
| let call_res = client.call(&ink_e2e::alice(), &message).dry_run().await; | ||
|
|
||
| // then | ||
| assert!(call_res.is_err()); | ||
|
|
||
| Ok(()) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| //! # Debugging Strategies | ||
| //! | ||
| //! This contract illustrates a number of strategies for debugging | ||
| //! contracts: | ||
| //! | ||
| //! * Emitting debugging events. | ||
| //! * The `pallet-revive` tracing API. | ||
| //! * Causing intentional reverts with a return value, in your contract. | ||
|
|
||
| #![cfg_attr(not(feature = "std"), no_std, no_main)] | ||
|
|
||
| #[ink::contract] | ||
| mod debugging_strategies { | ||
| use flipper::FlipperRef; | ||
| use ink::env::call::{ | ||
| ExecutionInput, | ||
| Selector, | ||
| build_call, | ||
| build_create, | ||
| }; | ||
| #[cfg(feature = "debug")] | ||
| use ink::prelude::{ | ||
| borrow::ToOwned, | ||
| format, | ||
| string::String, | ||
| }; | ||
|
|
||
| #[ink::event] | ||
| #[cfg(feature = "debug")] | ||
| pub struct DebugEvent { | ||
| message: String, | ||
| } | ||
|
|
||
| /// Storage of the contract. | ||
| #[ink(storage)] | ||
| #[derive(Default)] | ||
| pub struct DebuggingStrategies { | ||
| value: bool, | ||
| } | ||
|
|
||
| impl DebuggingStrategies { | ||
| #[ink(constructor)] | ||
| pub fn new() -> Self { | ||
| Self { value: true } | ||
| } | ||
|
|
||
| #[ink(message)] | ||
| pub fn get(&self) -> bool { | ||
| #[cfg(feature = "debug")] | ||
| self.env().emit_event(DebugEvent { | ||
| message: format!("received {:?}", self.env().transferred_value()) | ||
| .to_owned(), | ||
| }); | ||
| self.value | ||
| } | ||
|
|
||
| #[ink(message)] | ||
| pub fn intentional_revert(&self) { | ||
| #[cfg(feature = "debug")] | ||
| ink::env::return_value( | ||
| ink::env::ReturnFlags::REVERT, | ||
| &format!("reverting with info: {:?}", self.env().transferred_value()), | ||
| ); | ||
| } | ||
|
|
||
| #[ink(message, payable)] | ||
| pub fn instantiate_and_call(&mut self, code_hash: ink::H256) -> bool { | ||
| let create_params = build_create::<FlipperRef>() | ||
| .code_hash(code_hash) | ||
| .endowment(0.into()) | ||
| .exec_input(ExecutionInput::new(Selector::new(ink::selector_bytes!( | ||
| Abi::Ink, | ||
| "new" | ||
| )))) | ||
| .returns::<FlipperRef>() | ||
| .params(); | ||
|
|
||
| let other: FlipperRef = self.env() | ||
| .instantiate_contract(&create_params) | ||
| // todo | ||
| // we do this to make sure the instantiated contract is always at a | ||
| // different address | ||
| // .salt(self.env().addr().to_vec()) | ||
| .unwrap_or_else(|error| { | ||
| panic!( | ||
| "Received an error from `pallet-revive` while instantiating: {error:?}" | ||
| ) | ||
| }) | ||
| .unwrap_or_else(|error| { | ||
| panic!("Received a `LangError` while instantiating: {error:?}") | ||
| }); | ||
|
|
||
| let call = build_call() | ||
| .call(ink::ToAddr::to_addr(&other)) | ||
| .transferred_value(0.into()) | ||
| .exec_input(ExecutionInput::new(Selector::new(ink::selector_bytes!( | ||
| Abi::Ink, | ||
| "get" | ||
| )))) | ||
| .returns::<bool>() | ||
| .params(); | ||
|
|
||
| self.env() | ||
| .invoke_contract(&call) | ||
| .unwrap_or_else(|env_err| { | ||
| panic!("Received an error from the Environment: {env_err:?}") | ||
| }) | ||
| .unwrap_or_else(|lang_err| panic!("Received a `LangError`: {lang_err:?}")) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests; |
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.
Can we put this under the folder
use-case