diff --git a/listings/getting-started/bytearray/src/bytearray.cairo b/listings/getting-started/bytearray/src/bytearray.cairo index 54983847..43793bd1 100644 --- a/listings/getting-started/bytearray/src/bytearray.cairo +++ b/listings/getting-started/bytearray/src/bytearray.cairo @@ -1,10 +1,10 @@ -// ANCHOR: contract #[starknet::interface] pub trait IMessage { fn append(ref self: TContractState, str: ByteArray); fn prepend(ref self: TContractState, str: ByteArray); } +// ANCHOR: contract #[starknet::contract] pub mod MessageContract { #[storage] diff --git a/listings/getting-started/custom_type_serde/src/contract.cairo b/listings/getting-started/custom_type_serde/src/contract.cairo index 9ed1570d..beea5a07 100644 --- a/listings/getting-started/custom_type_serde/src/contract.cairo +++ b/listings/getting-started/custom_type_serde/src/contract.cairo @@ -1,3 +1,10 @@ +#[starknet::interface] +pub trait ISerdeCustomType { + fn person_input(ref self: TContractState, person: Person); + fn person_output(self: @TContractState) -> Person; +} + +// ANCHOR: contract // Deriving the `Serde` trait allows us to use // the Person type as an entrypoint parameter and return value #[derive(Drop, Serde)] @@ -6,12 +13,6 @@ pub struct Person { pub name: felt252 } -#[starknet::interface] -pub trait ISerdeCustomType { - fn person_input(ref self: TContractState, person: Person); - fn person_output(self: @TContractState) -> Person; -} - #[starknet::contract] pub mod SerdeCustomType { use super::Person; @@ -28,3 +29,45 @@ pub mod SerdeCustomType { } } } +// ANCHOR_END: contract + +#[cfg(test)] +mod tests { + use super::{ + SerdeCustomType, Person, ISerdeCustomTypeDispatcher, ISerdeCustomTypeDispatcherTrait + }; + use starknet::{ContractAddress, syscalls::deploy_syscall, SyscallResultTrait}; + + fn deploy() -> ISerdeCustomTypeDispatcher { + let (contract_address, _) = deploy_syscall( + SerdeCustomType::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap_syscall(); + ISerdeCustomTypeDispatcher { contract_address } + } + + #[test] + fn should_deploy() { + deploy(); + } + + #[test] + fn should_get_person_output() { + let contract = deploy(); + let expected_person = Person { age: 10, name: 'Joe' }; + let received_person = contract.person_output(); + let age_received = received_person.age; + let name_received = received_person.name; + + assert(age_received == expected_person.age, 'Wrong age value'); + assert(name_received == expected_person.name, 'Wrong name value'); + } + + #[test] + #[available_gas(2000000000)] + fn should_call_person_input() { + let contract = deploy(); + let expected_person = Person { age: 10, name: 'Joe' }; + contract.person_input(expected_person); + } +} diff --git a/listings/getting-started/custom_type_serde/src/lib.cairo b/listings/getting-started/custom_type_serde/src/lib.cairo index 11ada17a..6ccaa47d 100644 --- a/listings/getting-started/custom_type_serde/src/lib.cairo +++ b/listings/getting-started/custom_type_serde/src/lib.cairo @@ -1,4 +1 @@ mod contract; - -#[cfg(test)] -mod tests; diff --git a/listings/getting-started/custom_type_serde/src/tests.cairo b/listings/getting-started/custom_type_serde/src/tests.cairo deleted file mode 100644 index d8a23b1b..00000000 --- a/listings/getting-started/custom_type_serde/src/tests.cairo +++ /dev/null @@ -1,47 +0,0 @@ -// The purpose of these tests is to demonstrate the capability of using custom types as inputs and outputs in contract calls. -// Therefore, we are not employing getters and setters for managing the contract's state. - -mod tests { - use custom_type_serde::contract::{ - SerdeCustomType, Person, ISerdeCustomTypeDispatcher, ISerdeCustomTypeDispatcherTrait - }; - use starknet::ContractAddress; - use starknet::syscalls::deploy_syscall; - use starknet::SyscallResultTrait; - - fn deploy() -> ISerdeCustomTypeDispatcher { - let calldata: Array = array![]; - let (address0, _) = deploy_syscall( - SerdeCustomType::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap_syscall(); - ISerdeCustomTypeDispatcher { contract_address: address0 } - } - - #[test] - #[available_gas(2000000000)] - fn should_deploy() { - deploy(); - } - - #[test] - #[available_gas(2000000000)] - fn should_get_person_output() { - let contract = deploy(); - let expected_person = Person { age: 10, name: 'Joe' }; - let received_person = contract.person_output(); - let age_received = received_person.age; - let name_received = received_person.name; - - assert(age_received == expected_person.age, 'Wrong age value'); - assert(name_received == expected_person.name, 'Wrong name value'); - } - - #[test] - #[available_gas(2000000000)] - fn should_call_person_input() { - let contract = deploy(); - let expected_person = Person { age: 10, name: 'Joe' }; - let received_person = contract.person_input(expected_person); - } -} diff --git a/listings/getting-started/errors/src/custom_errors.cairo b/listings/getting-started/errors/src/custom_errors.cairo index f8f4a24a..eca2e00c 100644 --- a/listings/getting-started/errors/src/custom_errors.cairo +++ b/listings/getting-started/errors/src/custom_errors.cairo @@ -1,14 +1,15 @@ -pub mod Errors { - pub const NOT_POSITIVE: felt252 = 'must be greater than 0'; - pub const NOT_NULL: felt252 = 'must not be null'; -} - #[starknet::interface] pub trait ICustomErrorsExample { fn test_assert(self: @TContractState, i: u256); fn test_panic(self: @TContractState, i: u256); } +// ANCHOR: contract +pub mod Errors { + pub const NOT_POSITIVE: felt252 = 'must be greater than 0'; + pub const NOT_NULL: felt252 = 'must not be null'; +} + #[starknet::contract] pub mod CustomErrorsExample { use super::Errors; @@ -29,3 +30,34 @@ pub mod CustomErrorsExample { } } } +// ANCHOR_END: contract + +#[cfg(test)] +mod test { + use super::{ + CustomErrorsExample, ICustomErrorsExampleDispatcher, ICustomErrorsExampleDispatcherTrait + }; + use starknet::{ContractAddress, SyscallResultTrait, syscalls::deploy_syscall}; + + fn deploy() -> ICustomErrorsExampleDispatcher { + let (contract_address, _) = deploy_syscall( + CustomErrorsExample::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap_syscall(); + ICustomErrorsExampleDispatcher { contract_address } + } + + #[test] + #[should_panic(expected: ('must not be null', 'ENTRYPOINT_FAILED'))] + fn should_panic() { + let contract = deploy(); + contract.test_panic(0); + } + + #[test] + #[should_panic(expected: ('must be greater than 0', 'ENTRYPOINT_FAILED'))] + fn should_assert() { + let contract = deploy(); + contract.test_assert(0); + } +} diff --git a/listings/getting-started/errors/src/lib.cairo b/listings/getting-started/errors/src/lib.cairo index 95903457..707ead5f 100644 --- a/listings/getting-started/errors/src/lib.cairo +++ b/listings/getting-started/errors/src/lib.cairo @@ -1,6 +1,3 @@ mod simple_errors; mod custom_errors; mod vault_errors; - -#[cfg(test)] -mod tests; diff --git a/listings/getting-started/errors/src/simple_errors.cairo b/listings/getting-started/errors/src/simple_errors.cairo index 10f2645d..28886b0a 100644 --- a/listings/getting-started/errors/src/simple_errors.cairo +++ b/listings/getting-started/errors/src/simple_errors.cairo @@ -4,6 +4,7 @@ pub trait IErrorsExample { fn test_panic(self: @TContractState, i: u256); } +// ANCHOR: contract #[starknet::contract] pub mod ErrorsExample { #[storage] @@ -25,3 +26,32 @@ pub mod ErrorsExample { } } } +// ANCHOR_END: contract + +#[cfg(test)] +mod test { + use super::{ErrorsExample, IErrorsExampleDispatcher, IErrorsExampleDispatcherTrait}; + use starknet::{ContractAddress, SyscallResultTrait, syscalls::deploy_syscall}; + + fn deploy() -> IErrorsExampleDispatcher { + let (contract_address, _) = deploy_syscall( + ErrorsExample::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap_syscall(); + IErrorsExampleDispatcher { contract_address } + } + + #[test] + #[should_panic(expected: ('i must not be 0', 'ENTRYPOINT_FAILED'))] + fn should_panic() { + let contract = deploy(); + contract.test_panic(0); + } + + #[test] + #[should_panic(expected: ('i must be greater than 0', 'ENTRYPOINT_FAILED'))] + fn should_assert() { + let contract = deploy(); + contract.test_assert(0); + } +} diff --git a/listings/getting-started/errors/src/tests.cairo b/listings/getting-started/errors/src/tests.cairo deleted file mode 100644 index 361dba07..00000000 --- a/listings/getting-started/errors/src/tests.cairo +++ /dev/null @@ -1,2 +0,0 @@ -mod tests { // TODO -} diff --git a/listings/getting-started/errors/src/vault_errors.cairo b/listings/getting-started/errors/src/vault_errors.cairo index 71bc8724..4f34b951 100644 --- a/listings/getting-started/errors/src/vault_errors.cairo +++ b/listings/getting-started/errors/src/vault_errors.cairo @@ -1,14 +1,15 @@ -pub mod VaultErrors { - pub const INSUFFICIENT_BALANCE: felt252 = 'insufficient_balance'; -// you can define more errors here -} - #[starknet::interface] pub trait IVaultErrorsExample { fn deposit(ref self: TContractState, amount: u256); fn withdraw(ref self: TContractState, amount: u256); } +// ANCHOR: contract +pub mod VaultErrors { + pub const INSUFFICIENT_BALANCE: felt252 = 'insufficient_balance'; +// you can define more errors here +} + #[starknet::contract] pub mod VaultErrorsExample { use super::VaultErrors; @@ -32,7 +33,7 @@ pub mod VaultErrorsExample { assert(balance >= amount, VaultErrors::INSUFFICIENT_BALANCE); // Or using panic: - if (balance >= amount) { + if (balance < amount) { core::panic_with_felt252(VaultErrors::INSUFFICIENT_BALANCE); } @@ -42,3 +43,35 @@ pub mod VaultErrorsExample { } } } +// ANCHOR_END: contract + +#[cfg(test)] +mod test { + use super::{ + VaultErrorsExample, IVaultErrorsExampleDispatcher, IVaultErrorsExampleDispatcherTrait + }; + use starknet::{ContractAddress, SyscallResultTrait, syscalls::deploy_syscall}; + + fn deploy() -> IVaultErrorsExampleDispatcher { + let (contract_address, _) = deploy_syscall( + VaultErrorsExample::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap_syscall(); + IVaultErrorsExampleDispatcher { contract_address } + } + + #[test] + fn should_deposit_and_withdraw() { + let mut contract = deploy(); + contract.deposit(10); + contract.withdraw(5); + } + + #[test] + #[should_panic(expected: ('insufficient_balance', 'ENTRYPOINT_FAILED'))] + fn should_panic_on_insufficient_balance() { + let mut contract = deploy(); + contract.deposit(10); + contract.withdraw(15); + } +} diff --git a/listings/getting-started/events/src/counter.cairo b/listings/getting-started/events/src/counter.cairo index 7e93def1..fa4a0b39 100644 --- a/listings/getting-started/events/src/counter.cairo +++ b/listings/getting-started/events/src/counter.cairo @@ -1,7 +1,7 @@ // ANCHOR: contract #[starknet::interface] pub trait IEventCounter { - fn increment(ref self: TContractState); + fn increment(ref self: TContractState, amount: u128); } #[starknet::contract] @@ -15,38 +15,37 @@ pub mod EventCounter { } #[event] - #[derive(Drop, starknet::Event)] + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] // The event enum must be annotated with the `#[event]` attribute. - // It must also derive the `Drop` and `starknet::Event` traits. - enum Event { + // It must also derive atleast `Drop` and `starknet::Event` traits. + pub enum Event { CounterIncreased: CounterIncreased, UserIncreaseCounter: UserIncreaseCounter } // By deriving the `starknet::Event` trait, we indicate to the compiler that // this struct will be used when emitting events. - #[derive(Drop, starknet::Event)] - struct CounterIncreased { - amount: u128 + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct CounterIncreased { + pub amount: u128 } - #[derive(Drop, starknet::Event)] - struct UserIncreaseCounter { + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct UserIncreaseCounter { // The `#[key]` attribute indicates that this event will be indexed. + // You can also use `#[flat]` for nested structs. #[key] - user: ContractAddress, - new_value: u128, + pub user: ContractAddress, + pub new_value: u128, } #[abi(embed_v0)] impl EventCounter of super::IEventCounter { - fn increment(ref self: ContractState) { - let mut counter = self.counter.read(); - counter += 1; - self.counter.write(counter); + fn increment(ref self: ContractState, amount: u128) { + self.counter.write(self.counter.read() + amount); // Emit event // ANCHOR: emit - self.emit(Event::CounterIncreased(CounterIncreased { amount: 1 })); + self.emit(Event::CounterIncreased(CounterIncreased { amount })); self .emit( Event::UserIncreaseCounter( @@ -61,4 +60,51 @@ pub mod EventCounter { } // ANCHOR_END: contract +#[cfg(test)] +mod tests { + use super::{ + EventCounter, + EventCounter::{ + counterContractMemberStateTrait, Event, CounterIncreased, UserIncreaseCounter + }, + IEventCounterDispatcherTrait, IEventCounterDispatcher + }; + use starknet::{ + ContractAddress, contract_address_const, SyscallResultTrait, syscalls::deploy_syscall + }; + use starknet::testing::{set_contract_address, set_account_contract_address}; + #[test] + fn test_increment_events() { + let (contract_address, _) = deploy_syscall( + EventCounter::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap_syscall(); + let mut contract = IEventCounterDispatcher { contract_address }; + let state = EventCounter::contract_state_for_testing(); + + let amount = 10; + let caller = contract_address_const::<'caller'>(); + + // fake caller + set_contract_address(caller); + contract.increment(amount); + // set back to the contract for reading state + set_contract_address(contract_address); + assert_eq!(state.counter.read(), amount); + + // Notice the order: the first event emitted is the first to be popped. + /// ANCHOR: test_event + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::CounterIncreased(CounterIncreased { amount })) + ); + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::UserIncreaseCounter(UserIncreaseCounter { user: caller, new_value: amount }) + ) + ); + // ANCHOR_END: test_events + } +} diff --git a/listings/getting-started/events/src/lib.cairo b/listings/getting-started/events/src/lib.cairo index 9643300d..f5271637 100644 --- a/listings/getting-started/events/src/lib.cairo +++ b/listings/getting-started/events/src/lib.cairo @@ -1,4 +1 @@ mod counter; - -#[cfg(test)] -mod tests; diff --git a/listings/getting-started/events/src/tests.cairo b/listings/getting-started/events/src/tests.cairo deleted file mode 100644 index 361dba07..00000000 --- a/listings/getting-started/events/src/tests.cairo +++ /dev/null @@ -1,2 +0,0 @@ -mod tests { // TODO -} diff --git a/listings/getting-started/storage/src/contract.cairo b/listings/getting-started/storage/src/contract.cairo index 0c57e18c..44ec565c 100644 --- a/listings/getting-started/storage/src/contract.cairo +++ b/listings/getting-started/storage/src/contract.cairo @@ -18,9 +18,6 @@ mod test { aContractMemberStateTrait, bContractMemberStateTrait, cContractMemberStateTrait }; - #[starknet::interface] - trait ITestContract {} - #[test] fn test_can_deploy() { let (_contract_address, _) = deploy_syscall( diff --git a/listings/getting-started/storing_custom_types/src/contract.cairo b/listings/getting-started/storing_custom_types/src/contract.cairo index ab1e90d6..f3c80c98 100644 --- a/listings/getting-started/storing_custom_types/src/contract.cairo +++ b/listings/getting-started/storing_custom_types/src/contract.cairo @@ -3,6 +3,7 @@ pub trait IStoringCustomType { fn set_person(ref self: TContractState, person: Person); } +// ANCHOR: contract // Deriving the starknet::Store trait // allows us to store the `Person` struct in the contract's storage. #[derive(Drop, Serde, Copy, starknet::Store)] @@ -27,3 +28,25 @@ pub mod StoringCustomType { } } } +// ANCHOR_END: contract + +#[cfg(test)] +mod tests { + use super::{ + IStoringCustomType, StoringCustomType, Person, + StoringCustomType::personContractMemberStateTrait + }; + + #[test] + fn can_call_set_person() { + let mut state = StoringCustomType::contract_state_for_testing(); + + let person = Person { age: 10, name: 'Joe' }; + + state.set_person(person); + let read_person = state.person.read(); + + assert(person.age == read_person.age, 'wrong age'); + assert(person.name == read_person.name, 'wrong name'); + } +} diff --git a/listings/getting-started/storing_custom_types/src/lib.cairo b/listings/getting-started/storing_custom_types/src/lib.cairo index 11ada17a..6ccaa47d 100644 --- a/listings/getting-started/storing_custom_types/src/lib.cairo +++ b/listings/getting-started/storing_custom_types/src/lib.cairo @@ -1,4 +1 @@ mod contract; - -#[cfg(test)] -mod tests; diff --git a/listings/getting-started/storing_custom_types/src/tests.cairo b/listings/getting-started/storing_custom_types/src/tests.cairo deleted file mode 100644 index 5209c59d..00000000 --- a/listings/getting-started/storing_custom_types/src/tests.cairo +++ /dev/null @@ -1,30 +0,0 @@ -// The purpose of these tests is to demonstrate the capability to store custom types in the contract's state. - -mod tests { - use storing_custom_types::contract::{ - IStoringCustomType, StoringCustomType, Person, - StoringCustomType::personContractMemberStateTrait - }; - - use starknet::{ContractAddress, contract_address_const, testing::{set_contract_address}}; - - fn setup() -> StoringCustomType::ContractState { - let mut state = StoringCustomType::contract_state_for_testing(); - let contract_address = contract_address_const::<0x1>(); - set_contract_address(contract_address); - state - } - - #[test] - #[available_gas(2000000000)] - fn can_call_set_person() { - let mut state = setup(); - let person = Person { age: 10, name: 'Joe' }; - - state.set_person(person); - let read_person = state.person.read(); - - assert(person.age == read_person.age, 'wrong age'); - assert(person.name == read_person.name, 'wrong name'); - } -} diff --git a/src/ch00/basics/custom-types-in-entrypoints.md b/src/ch00/basics/custom-types-in-entrypoints.md index 544e99f2..0cac2514 100644 --- a/src/ch00/basics/custom-types-in-entrypoints.md +++ b/src/ch00/basics/custom-types-in-entrypoints.md @@ -3,6 +3,9 @@ Using custom types in entrypoints requires our type to implement the `Serde` trait. This is because when calling an entrypoint, the input is sent as an array of `felt252` to the entrypoint, and we need to be able to deserialize it into our custom type. Similarly, when returning a custom type from an entrypoint, we need to be able to serialize it into an array of `felt252`. Thankfully, we can just derive the `Serde` trait for our custom type. +The purpose is to only show the capability of using custom types as inputs and outputs in contract calls. +We are not employing getters and setters for managing the contract's state in this example for simplicity. + ```rust -{{#include ../../../listings/getting-started/custom_type_serde/src/contract.cairo}} +{{#rustdoc_include ../../../listings/getting-started/custom_type_serde/src/contract.cairo:contract}} ``` diff --git a/src/ch00/basics/errors.md b/src/ch00/basics/errors.md index 5f2924f2..73ae940c 100644 --- a/src/ch00/basics/errors.md +++ b/src/ch00/basics/errors.md @@ -13,10 +13,12 @@ To throw an error, use the `assert` or `panic` functions: It should be used when the condition to check is complex and for internal errors. It's similar to the `revert` statement in Solidity. (Use `panic_with_felt252` to be able to directly pass a felt252 as the error value) +The `assert_eq!`, `assert_ne!`, `assert_lt!`, `assert_le!`, `assert_gt!` and `assert_ge!` macros can be used as an `assert` shorthand to compare two values, but **only** in tests. In contract, you should only use the `assert` function. + Here's a simple example that demonstrates the use of these functions: ```rust -{{#include ../../../listings/getting-started/errors/src/simple_errors.cairo}} +{{#rustdoc_include ../../../listings/getting-started/errors/src/simple_errors.cairo:contract}} ``` ## Custom errors @@ -24,7 +26,7 @@ Here's a simple example that demonstrates the use of these functions: You can make error handling easier by defining your error codes in a specific module. ```rust -{{#include ../../../listings/getting-started/errors/src/custom_errors.cairo}} +{{#rustdoc_include ../../../listings/getting-started/errors/src/custom_errors.cairo:contract}} ``` ## Vault example @@ -32,5 +34,5 @@ You can make error handling easier by defining your error codes in a specific mo Here's another example that demonstrates the use of errors in a more complex contract: ```rust -{{#include ../../../listings/getting-started/errors/src/vault_errors.cairo}} +{{#rustdoc_include ../../../listings/getting-started/errors/src/vault_errors.cairo:contract}} ``` diff --git a/src/ch00/basics/events.md b/src/ch00/basics/events.md index 9052929b..4e0791aa 100644 --- a/src/ch00/basics/events.md +++ b/src/ch00/basics/events.md @@ -1,10 +1,10 @@ # Events Events are a way to emit data from a contract. All events must be defined in the `Event` enum, which must be annotated with the `#[event]` attribute. -An event is defined as struct that derives the `#[starknet::Event]` trait. The fields of that struct correspond to the data that will be emitted. An event can be indexed for easy and fast access when querying the data at a later time. Events data can be indexed by adding a `#[key]` attribute to a field member. +An event is defined as a struct that derives the `#[starknet::Event]` trait. The fields of that struct correspond to the data that will be emitted. An event can be indexed for easy and fast access when querying the data at a later time, by adding a `#[key]` attribute to a field member. Here's a simple example of a contract using events that emit an event each time a counter is incremented by the "increment" function: ```rust -{{#include ../../../listings/getting-started/events/src/counter.cairo:contract}} +{{#rustdoc_include ../../../listings/getting-started/events/src/counter.cairo:contract}} ``` diff --git a/src/ch00/basics/storing-custom-types.md b/src/ch00/basics/storing-custom-types.md index ea73972b..24cd3c79 100644 --- a/src/ch00/basics/storing-custom-types.md +++ b/src/ch00/basics/storing-custom-types.md @@ -3,5 +3,5 @@ While native types can be stored in a contract's storage without any additional work, custom types require a bit more work. This is because at compile time, the compiler does not know how to store custom types in storage. To solve this, we need to implement the `Store` trait for our custom type. Hopefully, we can just derive this trait for our custom type - unless it contains arrays or dictionaries. ```rust -{{#include ../../../listings/getting-started/storing_custom_types/src/contract.cairo}} +{{#rustdoc_include ../../../listings/getting-started/storing_custom_types/src/contract.cairo:contract}} ``` diff --git a/src/ch00/testing/contract-testing.md b/src/ch00/testing/contract-testing.md index e2ffa557..e0b0a3b1 100644 --- a/src/ch00/testing/contract-testing.md +++ b/src/ch00/testing/contract-testing.md @@ -40,10 +40,11 @@ You may also need the `info` module from the corelib, which allows you to access - `get_block_timestamp() -> u64` - `get_block_number() -> u64` - You can found the full list of functions in the [Starknet Corelib repo](https://github.com/starkware-libs/cairo/tree/main/corelib/src/starknet). You can also find a detailed explanation of testing in cairo in the [Cairo book - Chapter 9](https://book.cairo-lang.org/ch09-01-how-to-write-tests.html). + + ## Starknet Foundry