From 0de122b0301c19f98dd3b47c5154213658013dd8 Mon Sep 17 00:00:00 2001 From: Lawal Abubakar Babatunde Date: Thu, 10 Apr 2025 13:00:34 +0100 Subject: [PATCH 1/5] New contracts --- src/IHello.cairo | 11 ++++++++++ src/INumber.cairo | 5 +++++ src/hello.cairo | 55 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 src/IHello.cairo create mode 100644 src/INumber.cairo create mode 100644 src/hello.cairo diff --git a/src/IHello.cairo b/src/IHello.cairo new file mode 100644 index 0000000..0d1c511 --- /dev/null +++ b/src/IHello.cairo @@ -0,0 +1,11 @@ +/// Interface representing `HelloContract`. +/// This interface allows modification and retrieval of the contract balance. +#[starknet::interface] +pub trait IHelloStarknet { + /// Increase contract balance. + fn increase_balance(ref self: TContractState, amount: felt252); + /// Retrieve contract balance. + fn get_balance(self: @TContractState) -> felt252; + + fn add_and_subtract(ref self: TContractState, amount: felt252); +} \ No newline at end of file diff --git a/src/INumber.cairo b/src/INumber.cairo new file mode 100644 index 0000000..39a8d1d --- /dev/null +++ b/src/INumber.cairo @@ -0,0 +1,5 @@ +#[starknet::interface] +pub trait INumber { + fn set_number(ref self: TNothing, amount: u8); + fn get_number(self: @TNothing) -> u8; +} \ No newline at end of file diff --git a/src/hello.cairo b/src/hello.cairo new file mode 100644 index 0000000..fd7d3e2 --- /dev/null +++ b/src/hello.cairo @@ -0,0 +1,55 @@ +#[starknet::contract] +mod HelloStarknet { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use crate::IHello::IHelloStarknet; + use crate::INumber::INumber; + + #[storage] + struct Storage { + balance: felt252, + } + + #[generate_trait] + impl InternalTrait of InternalImpl { + fn _add(ref self: ContractState, amount: felt252) { + let old_balance = self.balance.read(); + self.balance.write(old_balance + amount); + } + + fn _subtract(ref self: ContractState, amount: felt252) { + let old_balance = self.balance.read(); + self.balance.write(old_balance - amount); + } + } + + fn add(amount: felt252) -> felt252 { + amount + 2 + } + + + #[abi(embed_v0)] + impl HelloStarknet of IHelloStarknet { + fn increase_balance(ref self: ContractState, amount: felt252) { + let upgraded_score = add(amount); + self._add(upgraded_score); + } + + fn get_balance(self: @ContractState) -> felt252 { + self.balance.read() + } + + fn add_and_subtract(ref self: ContractState, amount: felt252) { + self._add(amount); + self._subtract(amount); + } + } + + #[abi(embed_v0)] + impl INumberImpl of INumber { + fn set_number(ref self: ContractState, amount: u8) {} + fn get_number(self: @ContractState) -> u8 { + let number: u8 = 8; + number + } + } +} \ No newline at end of file From 6ea2e708e6fdaeb4fb7f4bf0b2a6b1fd17b4642a Mon Sep 17 00:00:00 2001 From: Lawal Abubakar Babatunde Date: Thu, 10 Apr 2025 13:09:55 +0100 Subject: [PATCH 2/5] chore: added lib file --- src/lib.cairo | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/lib.cairo diff --git a/src/lib.cairo b/src/lib.cairo new file mode 100644 index 0000000..312f36f --- /dev/null +++ b/src/lib.cairo @@ -0,0 +1,5 @@ + +pub mod hello; +pub mod IHello; +pub mod INumber; + From 292b5667ce3c41479c165dd95c132350f367a053 Mon Sep 17 00:00:00 2001 From: Lawal Abubakar Babatunde Date: Thu, 10 Apr 2025 13:22:48 +0100 Subject: [PATCH 3/5] chore: modified interface --- Scarb.toml | 45 +++++++++++++++++++++++++++++++++++++ src/INumber.cairo | 7 ++++-- tests/test_contract.cairo | 47 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 tests/test_contract.cairo diff --git a/Scarb.toml b/Scarb.toml index dcbf065..1ab8fce 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -6,10 +6,55 @@ edition = "2024_07" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html # [executable] [dependencies] +starknet = "2.11.2" [cairo] enable-gas = false [dev-dependencies] cairo_test = "2.11.2" +snforge_std = "0.39.0" +assert_macros = "2.11.2" # cairo_execute = "2.11.3" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[[target.starknet-contract]] +sierra = true + +[scripts] +test = "snforge test" + +[tool.scarb] +allow-prebuilt-plugins = ["snforge_std"] + +# Visit https://foundry-rs.github.io/starknet-foundry/appendix/scarb-toml.html for more information + +# [tool.snforge] # Define `snforge` tool section +# exit_first = true # Stop tests execution immediately upon the first failure +# fuzzer_runs = 1234 # Number of runs of the random fuzzer +# fuzzer_seed = 1111 # Seed for the random fuzzer + +# [[tool.snforge.fork]] # Used for fork testing +# name = "SOME_NAME" # Fork name +# url = "http://your.rpc.url" # Url of the RPC provider +# block_id.tag = "latest" # Block to fork from (block tag) + +# [[tool.snforge.fork]] +# name = "SOME_SECOND_NAME" +# url = "http://your.second.rpc.url" +# block_id.number = "123" # Block to fork from (block number) + +# [[tool.snforge.fork]] +# name = "SOME_THIRD_NAME" +# url = "http://your.third.rpc.url" +# block_id.hash = "0x123" # Block to fork from (block hash) + +# [profile.dev.cairo] # Configure Cairo compiler +# unstable-add-statements-code-locations-debug-info = true # Should be used if you want to use coverage +# unstable-add-statements-functions-debug-info = true # Should be used if you want to use coverage/profiler +# inlining-strategy = "avoid" # Should be used if you want to use coverage + +# [features] # Used for conditional compilation +# enable_for_tests = [] # Feature name and list of other features that should be enabled with it + diff --git a/src/INumber.cairo b/src/INumber.cairo index 39a8d1d..6ff1a57 100644 --- a/src/INumber.cairo +++ b/src/INumber.cairo @@ -1,5 +1,8 @@ +/// A trait for a contract that can store and retrieve a number #[starknet::interface] pub trait INumber { - fn set_number(ref self: TNothing, amount: u8); - fn get_number(self: @TNothing) -> u8; + /// Sets the number to the given value + fn set_number(ref self: TContractState, amount: u8); + /// Returns the current number + fn get_number(self: @TContractState) -> u8; } \ No newline at end of file diff --git a/tests/test_contract.cairo b/tests/test_contract.cairo new file mode 100644 index 0000000..2389d86 --- /dev/null +++ b/tests/test_contract.cairo @@ -0,0 +1,47 @@ +use starknet::ContractAddress; + +use snforge_std::{declare, ContractClassTrait, DeclareResultTrait}; + +use starknet_contract::IHelloStarknetSafeDispatcher; +use starknet_contract::IHelloStarknetSafeDispatcherTrait; +use starknet_contract::IHelloStarknetDispatcher; +use starknet_contract::IHelloStarknetDispatcherTrait; + +fn deploy_contract(name: ByteArray) -> ContractAddress { + let contract = declare(name).unwrap().contract_class(); + let (contract_address, _) = contract.deploy(@ArrayTrait::new()).unwrap(); + contract_address +} + +#[test] +fn test_increase_balance() { + let contract_address = deploy_contract("HelloStarknet"); + + let dispatcher = IHelloStarknetDispatcher { contract_address }; + + let balance_before = dispatcher.get_balance(); + assert(balance_before == 0, 'Invalid balance'); + + dispatcher.increase_balance(42); + + let balance_after = dispatcher.get_balance(); + assert(balance_after == 42, 'Invalid balance'); +} + +#[test] +#[feature("safe_dispatcher")] +fn test_cannot_increase_balance_with_zero_value() { + let contract_address = deploy_contract("HelloStarknet"); + + let safe_dispatcher = IHelloStarknetSafeDispatcher { contract_address }; + + let balance_before = safe_dispatcher.get_balance().unwrap(); + assert(balance_before == 0, 'Invalid balance'); + + match safe_dispatcher.increase_balance(0) { + Result::Ok(_) => core::panic_with_felt252('Should have panicked'), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'Amount cannot be 0', *panic_data.at(0)); + } + }; +} From dc92f71d47fcc45cd8dd0c78480a8ebc5799d550 Mon Sep 17 00:00:00 2001 From: Lawal Abubakar Babatunde Date: Thu, 10 Apr 2025 13:23:54 +0100 Subject: [PATCH 4/5] chore: fixed typo --- src/INumber.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/INumber.cairo b/src/INumber.cairo index 6ff1a57..eb4014b 100644 --- a/src/INumber.cairo +++ b/src/INumber.cairo @@ -1,6 +1,6 @@ /// A trait for a contract that can store and retrieve a number #[starknet::interface] -pub trait INumber { +pub trait INumber { /// Sets the number to the given value fn set_number(ref self: TContractState, amount: u8); /// Returns the current number From 0e6b54d3d420623f9ac0137faab4ed0d87ea33eb Mon Sep 17 00:00:00 2001 From: sprtd Date: Thu, 8 May 2025 14:44:21 +0100 Subject: [PATCH 5/5] refac: more on contract dispatchers --- Scarb.lock | 18 +++++++++ Scarb.toml | 10 +---- assignments/task_4.md | 7 ++++ src/aggregator.cairo | 77 +++++++++++++++++++++++++++++++++++++++ src/counter.cairo | 43 ++++++++++++++++++++++ src/killswitch.cairo | 35 ++++++++++++++++++ src/lib.cairo | 4 ++ tests/test_contract.cairo | 36 +++++++++--------- 8 files changed, 205 insertions(+), 25 deletions(-) create mode 100644 assignments/task_4.md create mode 100644 src/aggregator.cairo create mode 100644 src/counter.cairo create mode 100644 src/killswitch.cairo diff --git a/Scarb.lock b/Scarb.lock index 0448917..c502793 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -4,3 +4,21 @@ version = 1 [[package]] name = "cohort_4" version = "0.1.0" +dependencies = [ + "snforge_std", +] + +[[package]] +name = "snforge_scarb_plugin" +version = "0.40.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:7c3b21f6cdab14fc63e19f9e6789b6a3d44f5618ebcf02d03b397375304e1891" + +[[package]] +name = "snforge_std" +version = "0.40.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:0221bbe959eec72eb2e30be68df66c4ff5dcd924ec491f285c974e49671fabc0" +dependencies = [ + "snforge_scarb_plugin", +] diff --git a/Scarb.toml b/Scarb.toml index 1ab8fce..4419ef1 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -5,19 +5,13 @@ edition = "2024_07" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html # [executable] + [dependencies] starknet = "2.11.2" -[cairo] -enable-gas = false - [dev-dependencies] -cairo_test = "2.11.2" -snforge_std = "0.39.0" +snforge_std = "0.40.0" assert_macros = "2.11.2" -# cairo_execute = "2.11.3" - -# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html [[target.starknet-contract]] sierra = true diff --git a/assignments/task_4.md b/assignments/task_4.md new file mode 100644 index 0000000..9918984 --- /dev/null +++ b/assignments/task_4.md @@ -0,0 +1,7 @@ +# Write comprehensive tests for newly added contracts: +- `Counter` +- `KillSwitch` +- `Aggregator` + +### Objective +The goal of this assignment is to familiarize developers with dispatchers and how they can be leveraged to interact with other contracts outside the file where it was created thus making contracts composable. Through this assignment, you will learn how to send modify and retrieve state variables of other contracts diff --git a/src/aggregator.cairo b/src/aggregator.cairo new file mode 100644 index 0000000..3e4cbe8 --- /dev/null +++ b/src/aggregator.cairo @@ -0,0 +1,77 @@ +#[starknet::interface] +pub trait IAggregator { + /// Increase contract count. + fn increase_count(ref self: TContractState, amount: u32); + /// Increase contract count. + /// + fn increase_counter_count(ref self: TContractState, amount: u32); + + /// Retrieve contract count. + fn decrease_count_by_one(ref self: TContractState); + /// Retrieve contract count. + fn get_count(self: @TContractState) -> u32; + + fn activate_switch(ref self: TContractState); +} + +/// Simple contract for managing count. +#[starknet::contract] +mod Agggregator { + use cohort_4::counter::{ICounterDispatcher, ICounterDispatcherTrait}; + use cohort_4::killswitch::{IKillSwitchDispatcher, IKillSwitchDispatcherTrait}; + use starknet::ContractAddress; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + + + #[storage] + struct Storage { + count: u32, + counter: ContractAddress, + killswitch: ContractAddress, + } + + #[constructor] + fn constructor(ref self: ContractState, counter: ContractAddress, killswitch: ContractAddress) { + self.counter.write(counter); + self.killswitch.write(killswitch); + } + + + #[abi(embed_v0)] + impl AggregatorImpl of super::IAggregator { + fn increase_count(ref self: ContractState, amount: u32) { + assert(amount > 0, 'Amount cannot be 0'); + let counter = ICounterDispatcher { contract_address: self.counter.read() }; + let counter_count = counter.get_count(); + self.count.write(counter_count + amount); + } + + fn increase_counter_count(ref self: ContractState, amount: u32) { + let killswitch: IKillSwitchDispatcher = IKillSwitchDispatcher { + contract_address: self.killswitch.read(), + }; + assert(killswitch.get_status(), 'not active'); + ICounterDispatcher { contract_address: self.counter.read() }.increase_count(amount) + } + + fn decrease_count_by_one(ref self: ContractState) { + let current_count = self.get_count(); + assert!(current_count != 0, "Amount cannot be 0"); + self.count.write(current_count - 1); + } + + fn activate_switch(ref self: ContractState) { + let killswitch: IKillSwitchDispatcher = IKillSwitchDispatcher { + contract_address: self.killswitch.read(), + }; + + if !killswitch.get_status() { + killswitch.switch() + } + } + + fn get_count(self: @ContractState) -> u32 { + self.count.read() + } + } +} diff --git a/src/counter.cairo b/src/counter.cairo new file mode 100644 index 0000000..190c073 --- /dev/null +++ b/src/counter.cairo @@ -0,0 +1,43 @@ +/// Interface representing `Counter`. +/// This interface allows modification and retrieval of the contract count. +#[starknet::interface] +pub trait ICounter { + /// Increase contract count. + fn increase_count(ref self: TContractState, amount: u32); + + /// Decrease contract count by one + fn decrease_count_by_one(ref self: TContractState); + + /// Retrieve contract count. + fn get_count(self: @TContractState) -> u32; +} + +/// Simple contract for managing count. +#[starknet::contract] +mod Counter { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + + #[storage] + struct Storage { + count: u32, + } + + #[abi(embed_v0)] + impl CounterImpl of super::ICounter { + fn increase_count(ref self: ContractState, amount: u32) { + assert(amount > 0, 'Amount cannot be 0'); + let counter_count = self.get_count(); + self.count.write(counter_count + amount); + } + + fn decrease_count_by_one(ref self: ContractState) { + let current_count = self.get_count(); + assert(current_count > 0, 'Amount cannot be 0'); + self.count.write(current_count - 1); + } + + fn get_count(self: @ContractState) -> u32 { + self.count.read() + } + } +} diff --git a/src/killswitch.cairo b/src/killswitch.cairo new file mode 100644 index 0000000..6437088 --- /dev/null +++ b/src/killswitch.cairo @@ -0,0 +1,35 @@ +/// Interface representing `HelloContract`. +/// This interface allows modification and retrieval of the contract count. +#[starknet::interface] +pub trait IKillSwitch { + /// Increase contract count. + fn switch(ref self: TContractState); + + /// Retrieve contract count. + fn get_status(self: @TContractState) -> bool; +} + +/// Simple contract for managing count. +#[starknet::contract] +mod KillSwitch { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + + #[storage] + struct Storage { + status: bool, + } + + + #[abi(embed_v0)] + impl KillSwitchImpl of super::IKillSwitch { + fn switch(ref self: ContractState) { + // assert(amount != 0, 'Amount cannot be 0'); + self.status.write(!self.status.read()); + } + + + fn get_status(self: @ContractState) -> bool { + self.status.read() + } + } +} diff --git a/src/lib.cairo b/src/lib.cairo index 8ea2bb9..8b3993c 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1,6 +1,10 @@ pub mod hello; pub mod IHello; pub mod INumber; +pub mod counter; +pub mod killswitch; +pub mod aggregator; + fn main() { diff --git a/tests/test_contract.cairo b/tests/test_contract.cairo index 2389d86..9d6708a 100644 --- a/tests/test_contract.cairo +++ b/tests/test_contract.cairo @@ -2,46 +2,48 @@ use starknet::ContractAddress; use snforge_std::{declare, ContractClassTrait, DeclareResultTrait}; -use starknet_contract::IHelloStarknetSafeDispatcher; -use starknet_contract::IHelloStarknetSafeDispatcherTrait; -use starknet_contract::IHelloStarknetDispatcher; -use starknet_contract::IHelloStarknetDispatcherTrait; +use cohort_4::counter::{ICounterDispatcher, ICounterDispatcherTrait, ICounterSafeDispatcher, ICounterSafeDispatcherTrait}; -fn deploy_contract(name: ByteArray) -> ContractAddress { - let contract = declare(name).unwrap().contract_class(); +fn deploy_contract() -> ContractAddress { + let countract_name: ByteArray = "Counter"; + let contract = declare(countract_name).unwrap().contract_class(); let (contract_address, _) = contract.deploy(@ArrayTrait::new()).unwrap(); contract_address } #[test] -fn test_increase_balance() { - let contract_address = deploy_contract("HelloStarknet"); +fn test_increase_count() { + let contract_address = deploy_contract(); - let dispatcher = IHelloStarknetDispatcher { contract_address }; + let dispatcher = ICounterDispatcher { contract_address }; - let balance_before = dispatcher.get_balance(); + let balance_before = dispatcher.get_count(); assert(balance_before == 0, 'Invalid balance'); - dispatcher.increase_balance(42); + dispatcher.increase_count(42); - let balance_after = dispatcher.get_balance(); + let balance_after = dispatcher.get_count(); assert(balance_after == 42, 'Invalid balance'); } #[test] #[feature("safe_dispatcher")] fn test_cannot_increase_balance_with_zero_value() { - let contract_address = deploy_contract("HelloStarknet"); + let contract_address = deploy_contract(); - let safe_dispatcher = IHelloStarknetSafeDispatcher { contract_address }; + let dispatcher = ICounterDispatcher { contract_address }; - let balance_before = safe_dispatcher.get_balance().unwrap(); - assert(balance_before == 0, 'Invalid balance'); + let balance_before = dispatcher.get_count(); + assert(balance_before == 0 , 'Invalid balance'); + + let safe_dispatcher = ICounterSafeDispatcher { contract_address }; - match safe_dispatcher.increase_balance(0) { + match safe_dispatcher.increase_count(0) { Result::Ok(_) => core::panic_with_felt252('Should have panicked'), Result::Err(panic_data) => { assert(*panic_data.at(0) == 'Amount cannot be 0', *panic_data.at(0)); } }; + + }