From ad4220ccc9a57e2523134647d128c14ac74721fb Mon Sep 17 00:00:00 2001 From: Olorunshogo Moses BAMTEFA Date: Tue, 28 Apr 2026 17:42:18 +0100 Subject: [PATCH 1/7] feat: starknet contract and cairo program --- cairo_program/src/integer.cairo | 71 +++++++++++++++++++++++++++----- cairo_program/src/lib.cairo | 4 +- starknet_contracts/src/lib.cairo | 28 +++++++++++-- 3 files changed, 88 insertions(+), 15 deletions(-) diff --git a/cairo_program/src/integer.cairo b/cairo_program/src/integer.cairo index 9501eca..5133694 100644 --- a/cairo_program/src/integer.cairo +++ b/cairo_program/src/integer.cairo @@ -1,20 +1,71 @@ +use core::result; +use core::panic_with_felt252; + #[executable] fn main() { - let result: u8 = add_num(5, 6); - println!("the sum of x & y is: {}", result); - assert(result == 11, 'invalid sum logic'); + // Addition + let addResult: u32 = add_num(20, 7); + println!("the sum of x & y is: {}", addResult); + + // Subtraction + let sub_result: Result = sub_num(16, 9); + match sub_result { + Result::Ok(value) => { + println!("sub result is: {}", value); + // assert(sub_result == 7, 'Invalid difference logic'); + }, + Result::Err(err) => { panic_with_felt252(err); }, + } + + // Multiplication + let multiplication_result: u32 = multiply_num(5, 6); + println!("The multiplication of x and y is: {}", multiplication_result); - let sub_result: u8 = sub_num(10, 5); - println!("sub result is: {}", sub_result); - assert(sub_result == 5, 'invalid sub logic'); + // Division + let division_result = divide_num(0, 10); + match division_result { + Result::Ok(value) => { + println!("Dividing x and y will give you: {}", value); + }, + Result::Err(err) => { + panic_with_felt252(err); + }, + } +} + +#[derive(Drop)] +enum Result { + Ok: T, + Err: E, } // addition logic -fn add_num(x: u8, y: u8) -> u8 { - x + y +fn add_num(x: u32, y: u32) -> u32 { + let result: u32 = x + y; + return result; } // subtraction logic -fn sub_num(x: u8, y: u8) -> u8 { - return x - y; +fn sub_num(x: u32, y: u32) -> Result { + if x < y { + Result::Err('Subtraction error') + } else { + Result::Ok(x - y) + } } + +// multiplication logic +fn multiply_num(x: u32, y: u32) -> u32 { + x * y +} + +// division function +fn divide_num(x: u32, y: u32) -> Result { + if y == 0 { + Result::Err('Division by zero is not allowed') + } else { + Result::Ok(x / y) + } +} + + diff --git a/cairo_program/src/lib.cairo b/cairo_program/src/lib.cairo index acc7644..b1c3620 100644 --- a/cairo_program/src/lib.cairo +++ b/cairo_program/src/lib.cairo @@ -1,5 +1,5 @@ // mod hello_world; // mod short_string; -// mod integer; +mod integer; // mod bool; -mod bytearray; \ No newline at end of file +// mod bytearray; \ No newline at end of file diff --git a/starknet_contracts/src/lib.cairo b/starknet_contracts/src/lib.cairo index f1b82bb..b4b74be 100644 --- a/starknet_contracts/src/lib.cairo +++ b/starknet_contracts/src/lib.cairo @@ -1,4 +1,4 @@ -/// Interface representing `HelloContract`. +/// Interface representing `Counter`. /// This interface allows modification and retrieval of the contract's storage count. #[starknet::interface] pub trait ICounter { @@ -6,27 +6,49 @@ pub trait ICounter { fn increase_count(ref self: T, amount: u32); /// Retrieve count. fn get_count(self: @T) -> u32; + /// Retrieve owner. + fn get_owner(self: @T) -> starknet::ContractAddress; } -/// Simple contract for managing count. +/// Simple contract for managing count with ownership and events. #[starknet::contract] mod Counter { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use starknet::{ContractAddress, get_caller_address}; #[storage] struct Storage { count: u32, + owner: ContractAddress, + } + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + self.owner.write(owner); + } + + #[generate_trait] + impl PrivateImpl of PrivateTrait { + fn assert_only_owner(self: @ContractState) { + assert(get_caller_address() == self.owner.read(), 'Caller is not owner'); + } } #[abi(embed_v0)] impl CounterImpl of super::ICounter { fn increase_count(ref self: ContractState, amount: u32) { + self.assert_only_owner(); assert(amount != 0, 'Amount cannot be 0'); - self.count.write(self.count.read() + amount); + let new_count = self.count.read() + amount; + self.count.write(new_count); } fn get_count(self: @ContractState) -> u32 { self.count.read() } + + fn get_owner(self: @ContractState) -> starknet::ContractAddress { + self.owner.read() + } } } From 952c7d43842cccff8259d3541ffa1db33b5183e7 Mon Sep 17 00:00:00 2001 From: Olorunshogo Moses BAMTEFA Date: Wed, 29 Apr 2026 16:39:35 +0100 Subject: [PATCH 2/7] feat: adding other pure functions --- .gitignore | 2 +- README.md | 17 +- starknet_contracts/Scarb.lock | 8 +- starknet_contracts/Scarb.toml | 4 +- starknet_contracts/src/arithmetic.cairo | 13 ++ starknet_contracts/src/lib.cairo | 51 ++++- starknet_contracts/tests/test_counter.cairo | 224 ++++++++++++++++++++ 7 files changed, 307 insertions(+), 12 deletions(-) create mode 100644 starknet_contracts/src/arithmetic.cairo create mode 100644 starknet_contracts/tests/test_counter.cairo diff --git a/.gitignore b/.gitignore index eb5a316..2f7896d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -target +target/ diff --git a/README.md b/README.md index ece307b..a9dfd3c 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,20 @@ This Cairo Bootcamp is designed to: --- ## 📁 Repository Structure +The repository is organized into the following directories . -├── cairo_programs/ # Pure Cairo programs (stateless logic) +├── cairo_program/ # Pure Cairo programs (stateless logic) +│ ├── README.md +│ ├── Scarb.lock +│ ├── Scarb.toml +│ ├── src +│ └── target +├── README.md ├── starknet_contracts/ # Smart contracts (stateful logic) -└── README.md \ No newline at end of file +│ ├── Scarb.lock +│ ├── Scarb.toml +│ ├── snfoundry.toml +│ └── src +└── target + ├── CACHEDIR.TAG + └── cairo-language-server \ No newline at end of file diff --git a/starknet_contracts/Scarb.lock b/starknet_contracts/Scarb.lock index 6c8c64a..e7e0b2f 100644 --- a/starknet_contracts/Scarb.lock +++ b/starknet_contracts/Scarb.lock @@ -3,15 +3,15 @@ version = 1 [[package]] name = "snforge_scarb_plugin" -version = "0.56.0" +version = "0.59.0" source = "registry+https://scarbs.xyz/" -checksum = "sha256:d5f2d878f7ae36c8149c517e2d618a95d7833598a76083cb12ea3f00da06a618" +checksum = "sha256:871fba677c03b66a1bf40815dac0ab1b385eb1b9be6e6c3cf2ad9788eeb2b6bb" [[package]] name = "snforge_std" -version = "0.56.0" +version = "0.59.0" source = "registry+https://scarbs.xyz/" -checksum = "sha256:87dfe06ffd5cf1d5a075b7a5c8dff442b66d0eeaf27921140cf446213eb29a68" +checksum = "sha256:3620924fa08bd2d740b2b5b01ef86c8dab3d4b9c2206387c8dbdc8d2ec15133e" dependencies = [ "snforge_scarb_plugin", ] diff --git a/starknet_contracts/Scarb.toml b/starknet_contracts/Scarb.toml index 29cf20f..64d28a7 100644 --- a/starknet_contracts/Scarb.toml +++ b/starknet_contracts/Scarb.toml @@ -9,12 +9,14 @@ edition = "2024_07" starknet = "2.18.0" [dev-dependencies] -snforge_std = "0.56.0" +snforge_std = "0.59.0" assert_macros = "2.18.0" [[target.starknet-contract]] sierra = true +[lib] + [scripts] test = "snforge test" diff --git a/starknet_contracts/src/arithmetic.cairo b/starknet_contracts/src/arithmetic.cairo new file mode 100644 index 0000000..12097fa --- /dev/null +++ b/starknet_contracts/src/arithmetic.cairo @@ -0,0 +1,13 @@ +/// Adds two numbers and returns the result. +pub fn add_num(x: u32, y: u32) -> u32 { + x + y +} + +/// Subtracts y from x. Returns an error felt if x < y. +pub fn sub_num(x: u32, y: u32) -> Result { + if x < y { + Result::Err('Subtraction error') + } else { + Result::Ok(x - y) + } +} diff --git a/starknet_contracts/src/lib.cairo b/starknet_contracts/src/lib.cairo index b4b74be..49ff3b0 100644 --- a/starknet_contracts/src/lib.cairo +++ b/starknet_contracts/src/lib.cairo @@ -2,17 +2,16 @@ /// This interface allows modification and retrieval of the contract's storage count. #[starknet::interface] pub trait ICounter { - /// Increase count. fn increase_count(ref self: T, amount: u32); - /// Retrieve count. + fn decrease_count(ref self: T, amount: u32); + fn reset_count(ref self: T); fn get_count(self: @T) -> u32; - /// Retrieve owner. fn get_owner(self: @T) -> starknet::ContractAddress; } /// Simple contract for managing count with ownership and events. #[starknet::contract] -mod Counter { +pub mod Counter { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use starknet::{ContractAddress, get_caller_address}; @@ -22,6 +21,32 @@ mod Counter { owner: ContractAddress, } + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + CountIncreased: CountIncreased, + CountDecreased: CountDecreased, + CountReset: CountReset, + } + + // === Individual struct for each variants + #[derive(Drop, starknet::Event)] + pub struct CountIncreased { + pub amount: u32, + pub new_count: u32, + } + + #[derive(Drop, starknet::Event)] + pub struct CountDecreased { + pub amount: u32, + pub new_count: u32, + } + + #[derive(Drop, starknet::Event)] + pub struct CountReset { + pub previous_count: u32, + } + #[constructor] fn constructor(ref self: ContractState, owner: ContractAddress) { self.owner.write(owner); @@ -41,6 +66,24 @@ mod Counter { assert(amount != 0, 'Amount cannot be 0'); let new_count = self.count.read() + amount; self.count.write(new_count); + self.emit(Event::CountIncreased(CountIncreased { amount, new_count })); + } + + fn decrease_count(ref self: ContractState, amount: u32) { + self.assert_only_owner(); + assert(amount != 0, 'Amount cannot be 0'); + let current_count = self.count.read(); + assert(current_count >= amount, 'Amount exceeds count'); + let new_count = current_count - amount; + self.count.write(new_count); + self.emit(Event::CountDecreased(CountDecreased { amount, new_count })); + } + + fn reset_count(ref self: ContractState) { + self.assert_only_owner(); + let previous_count = self.count.read(); + self.count.write(0); + self.emit(Event::CountReset(CountReset { previous_count })); } fn get_count(self: @ContractState) -> u32 { diff --git a/starknet_contracts/tests/test_counter.cairo b/starknet_contracts/tests/test_counter.cairo new file mode 100644 index 0000000..6a7c129 --- /dev/null +++ b/starknet_contracts/tests/test_counter.cairo @@ -0,0 +1,224 @@ +use starknet::SyscallResultTrait; +use starknet::ContractAddress; +use snforge_std::{ + declare, ContractClassTrait, DeclareResultTrait, start_cheat_caller_address, + stop_cheat_caller_address, spy_events, EventSpyAssertionsTrait, +}; +use starknet_contracts::{ICounterDispatcher, ICounterDispatcherTrait, ICounterSafeDispatcher, ICounterSafeDispatcherTrait}; +use starknet_contracts::Counter::{CountIncreased, CountDecreased, CountReset, Event}; + +// === Helpers +fn OWNER() -> ContractAddress { + 123_felt252.try_into().unwrap() +} + +fn NOT_OWNER() -> ContractAddress { + 456_felt252.try_into().unwrap() +} + +fn deploy_counter() -> (ICounterDispatcher, ICounterSafeDispatcher, ContractAddress) { + let contract = declare("Counter").unwrap_syscall().contract_class(); + let mut calldata = array![OWNER().into()]; + let (contract_address, _) = contract.deploy(@calldata).unwrap_syscall(); + ( + ICounterDispatcher { contract_address }, + ICounterSafeDispatcher { contract_address }, + contract_address, + ) +} + +// === Deployment + +#[test] +fn test_initial_state() { + let (dispatcher, _, _) = deploy_counter(); + assert(dispatcher.get_count() == 0, 'Initial count should be 0'); + assert(dispatcher.get_owner() == OWNER(), 'Owner should match constructor'); +} + +// === increase_count +#[test] +fn test_increase_count() { + let (dispatcher, _, contract_address) = deploy_counter(); + start_cheat_caller_address(contract_address, OWNER()); + dispatcher.increase_count(5); + assert(dispatcher.get_count() == 5, 'Count should be 5'); + stop_cheat_caller_address(contract_address); +} + +#[test] +fn test_increase_count_accumulates() { + let (dispatcher, _, contract_address) = deploy_counter(); + start_cheat_caller_address(contract_address, OWNER()); + dispatcher.increase_count(3); + dispatcher.increase_count(7); + assert(dispatcher.get_count() == 10, 'Count should be 10'); + stop_cheat_caller_address(contract_address); +} + +#[test] +#[feature("safe_dispatcher")] +fn test_increase_count_zero_amount_fails() { + let (_, safe_dispatcher, contract_address) = deploy_counter(); + start_cheat_caller_address(contract_address, OWNER()); + 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', 'Wrong error message'); + }, + }; + stop_cheat_caller_address(contract_address); +} + +#[test] +#[feature("safe_dispatcher")] +fn test_increase_count_not_owner_fails() { + let (_, safe_dispatcher, contract_address) = deploy_counter(); + start_cheat_caller_address(contract_address, NOT_OWNER()); + match safe_dispatcher.increase_count(5) { + Result::Ok(_) => core::panic_with_felt252('Should have panicked'), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'Caller is not owner', 'Wrong error message'); + }, + }; + stop_cheat_caller_address(contract_address); +} + +// === decrease_count + +#[test] +fn test_decrease_count() { + let (dispatcher, _, contract_address) = deploy_counter(); + start_cheat_caller_address(contract_address, OWNER()); + dispatcher.increase_count(10); + dispatcher.decrease_count(4); + assert(dispatcher.get_count() == 6, 'Count should be 6'); + stop_cheat_caller_address(contract_address); +} + +#[test] +#[feature("safe_dispatcher")] +fn test_decrease_count_zero_amount_fails() { + let (_, safe_dispatcher, contract_address) = deploy_counter(); + start_cheat_caller_address(contract_address, OWNER()); + match safe_dispatcher.decrease_count(0) { + Result::Ok(_) => core::panic_with_felt252('Should have panicked'), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'Amount cannot be 0', 'Wrong error message'); + }, + }; + stop_cheat_caller_address(contract_address); +} + +#[test] +#[feature("safe_dispatcher")] +fn test_decrease_count_exceeds_fails() { + let (dispatcher, safe_dispatcher, contract_address) = deploy_counter(); + start_cheat_caller_address(contract_address, OWNER()); + dispatcher.increase_count(3); + match safe_dispatcher.decrease_count(10) { + Result::Ok(_) => core::panic_with_felt252('Should have panicked'), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'Amount exceeds count', 'Wrong error message'); + }, + }; + stop_cheat_caller_address(contract_address); +} + +#[test] +#[feature("safe_dispatcher")] +fn test_decrease_count_not_owner_fails() { + let (_, safe_dispatcher, contract_address) = deploy_counter(); + start_cheat_caller_address(contract_address, NOT_OWNER()); + match safe_dispatcher.decrease_count(5) { + Result::Ok(_) => core::panic_with_felt252('Should have panicked'), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'Caller is not owner', 'Wrong error message'); + }, + }; + stop_cheat_caller_address(contract_address); +} + +// === reset_count + +#[test] +fn test_reset_count() { + let (dispatcher, _, contract_address) = deploy_counter(); + start_cheat_caller_address(contract_address, OWNER()); + dispatcher.increase_count(20); + dispatcher.reset_count(); + assert(dispatcher.get_count() == 0, 'Count should be 0 after reset'); + stop_cheat_caller_address(contract_address); +} + +#[test] +#[feature("safe_dispatcher")] +fn test_reset_count_not_owner_fails() { + let (_, safe_dispatcher, contract_address) = deploy_counter(); + start_cheat_caller_address(contract_address, NOT_OWNER()); + match safe_dispatcher.reset_count() { + Result::Ok(_) => core::panic_with_felt252('Should have panicked'), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'Caller is not owner', 'Wrong error message'); + }, + }; + stop_cheat_caller_address(contract_address); +} + +// === Events + +#[test] +fn test_increase_count_emits_event() { + let (dispatcher, _, contract_address) = deploy_counter(); + let mut spy = spy_events(); + start_cheat_caller_address(contract_address, OWNER()); + dispatcher.increase_count(5); + stop_cheat_caller_address(contract_address); + + spy.assert_emitted( + @array![ + ( + contract_address, + Event::CountIncreased(CountIncreased { amount: 5, new_count: 5 }), + ), + ], + ); +} + +#[test] +fn test_decrease_count_emits_event() { + let (dispatcher, _, contract_address) = deploy_counter(); + let mut spy = spy_events(); + start_cheat_caller_address(contract_address, OWNER()); + dispatcher.increase_count(10); + dispatcher.decrease_count(3); + stop_cheat_caller_address(contract_address); + + spy.assert_emitted( + @array![ + ( + contract_address, + Event::CountDecreased(CountDecreased { amount: 3, new_count: 7 }), + ), + ], + ); +} + +#[test] +fn test_reset_count_emits_event() { + let (dispatcher, _, contract_address) = deploy_counter(); + let mut spy = spy_events(); + start_cheat_caller_address(contract_address, OWNER()); + dispatcher.increase_count(8); + dispatcher.reset_count(); + stop_cheat_caller_address(contract_address); + + spy.assert_emitted( + @array![ + ( + contract_address, + Event::CountReset(CountReset { previous_count: 8 }), + ), + ], + ); +} From 62bb0ec9edd866fdf5588125a3ebee7f16dd6330 Mon Sep 17 00:00:00 2001 From: Olorunshogo Moses BAMTEFA Date: Thu, 30 Apr 2026 14:47:00 +0100 Subject: [PATCH 3/7] fix: pure functions are done --- .vscode/settings.json | 3 - cairo_program/.vscode/settings.json | 3 - cairo_program/Scarb.lock | 18 ++++ cairo_program/Scarb.toml | 16 ++-- cairo_program/src/integer.cairo | 105 +++++++++++------------- cairo_program/src/lib.cairo | 2 +- cairo_runner/Scarb.lock | 13 +++ cairo_runner/Scarb.toml | 13 +++ cairo_runner/src/lib.cairo | 54 ++++++++++++ starknet_contracts/Scarb.lock | 5 ++ starknet_contracts/Scarb.toml | 1 + starknet_contracts/src/arithmetic.cairo | 13 --- starknet_contracts/src/lib.cairo | 6 +- 13 files changed, 166 insertions(+), 86 deletions(-) delete mode 100644 .vscode/settings.json delete mode 100644 cairo_program/.vscode/settings.json create mode 100644 cairo_runner/Scarb.lock create mode 100644 cairo_runner/Scarb.toml create mode 100644 cairo_runner/src/lib.cairo delete mode 100644 starknet_contracts/src/arithmetic.cairo diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index edc55ce..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "snyk.advanced.autoSelectOrganization": true -} \ No newline at end of file diff --git a/cairo_program/.vscode/settings.json b/cairo_program/.vscode/settings.json deleted file mode 100644 index edc55ce..0000000 --- a/cairo_program/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "snyk.advanced.autoSelectOrganization": true -} \ No newline at end of file diff --git a/cairo_program/Scarb.lock b/cairo_program/Scarb.lock index b4ff584..dbc84c8 100644 --- a/cairo_program/Scarb.lock +++ b/cairo_program/Scarb.lock @@ -4,3 +4,21 @@ version = 1 [[package]] name = "cairo_6" version = "0.1.0" +dependencies = [ + "snforge_std", +] + +[[package]] +name = "snforge_scarb_plugin" +version = "0.59.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:871fba677c03b66a1bf40815dac0ab1b385eb1b9be6e6c3cf2ad9788eeb2b6bb" + +[[package]] +name = "snforge_std" +version = "0.59.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:3620924fa08bd2d740b2b5b01ef86c8dab3d4b9c2206387c8dbdc8d2ec15133e" +dependencies = [ + "snforge_scarb_plugin", +] diff --git a/cairo_program/Scarb.toml b/cairo_program/Scarb.toml index b6c900a..c044775 100644 --- a/cairo_program/Scarb.toml +++ b/cairo_program/Scarb.toml @@ -3,14 +3,18 @@ name = "cairo_6" version = "0.1.0" edition = "2025_12" -# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html +[lib] -[executable] +[dependencies] -[cairo] -enable-gas = false +[dev-dependencies] +snforge_std = "0.59.0" +assert_macros = "2.18.0" -[dependencies] -cairo_execute = "2.18.0" +[scripts] +test = "snforge test" + +[tool.scarb] +allow-prebuilt-plugins = ["snforge_std"] diff --git a/cairo_program/src/integer.cairo b/cairo_program/src/integer.cairo index 5133694..5c3c6d9 100644 --- a/cairo_program/src/integer.cairo +++ b/cairo_program/src/integer.cairo @@ -1,71 +1,62 @@ -use core::result; -use core::panic_with_felt252; - -#[executable] -fn main() { - // Addition - let addResult: u32 = add_num(20, 7); - println!("the sum of x & y is: {}", addResult); +// === Arithmetic Functions +pub fn add_num(x: u32, y: u32) -> Result { + let result: u256 = x.into() + y.into(); + result.try_into().ok_or('Addition overflow') +} - // Subtraction - let sub_result: Result = sub_num(16, 9); - match sub_result { - Result::Ok(value) => { - println!("sub result is: {}", value); - // assert(sub_result == 7, 'Invalid difference logic'); - }, - Result::Err(err) => { panic_with_felt252(err); }, +pub fn sub_num(x: u32, y: u32) -> Result { + if x < y { + Result::Err('Subtraction underflow') + } else { + Result::Ok(x - y) } +} - // Multiplication - let multiplication_result: u32 = multiply_num(5, 6); - println!("The multiplication of x and y is: {}", multiplication_result); +pub fn multiply_num(x: u32, y: u32) -> Result { + let result: u256 = x.into() * y.into(); + result.try_into().ok_or('Multiplication overflow') +} - // Division - let division_result = divide_num(0, 10); - match division_result { - Result::Ok(value) => { - println!("Dividing x and y will give you: {}", value); - }, - Result::Err(err) => { - panic_with_felt252(err); - }, +pub fn divide_num(x: u32, y: u32) -> Result { + if y == 0 { + Result::Err('Division by zero') + } else { + Result::Ok(x / y) } } -#[derive(Drop)] -enum Result { - Ok: T, - Err: E, -} +// === Tests +#[cfg(test)] +mod tests { + use super::{add_num, sub_num, multiply_num, divide_num}; -// addition logic -fn add_num(x: u32, y: u32) -> u32 { - let result: u32 = x + y; - return result; -} + #[test] + fn test_add_num() { + assert!(add_num(20, 7) == Result::Ok(27), "add failed"); + } -// subtraction logic -fn sub_num(x: u32, y: u32) -> Result { - if x < y { - Result::Err('Subtraction error') - } else { - Result::Ok(x - y) + #[test] + fn test_sub_num_ok() { + assert!(sub_num(16, 9) == Result::Ok(7), "sub failed"); } -} -// multiplication logic -fn multiply_num(x: u32, y: u32) -> u32 { - x * y -} + #[test] + fn test_sub_num_underflow() { + assert!(sub_num(3, 10) == Result::Err('Subtraction underflow'), "underflow check failed"); + } -// division function -fn divide_num(x: u32, y: u32) -> Result { - if y == 0 { - Result::Err('Division by zero is not allowed') - } else { - Result::Ok(x / y) - } -} + #[test] + fn test_multiply_num() { + assert!(multiply_num(5, 6) == Result::Ok(30), "multiply failed"); + } + #[test] + fn test_divide_num_ok() { + assert!(divide_num(20, 5) == Result::Ok(4), "divide failed"); + } + #[test] + fn test_divide_by_zero() { + assert!(divide_num(10, 0) == Result::Err('Division by zero'), "div zero check failed"); + } +} diff --git a/cairo_program/src/lib.cairo b/cairo_program/src/lib.cairo index b1c3620..56cad6d 100644 --- a/cairo_program/src/lib.cairo +++ b/cairo_program/src/lib.cairo @@ -1,5 +1,5 @@ // mod hello_world; // mod short_string; -mod integer; +pub mod integer; // mod bool; // mod bytearray; \ No newline at end of file diff --git a/cairo_runner/Scarb.lock b/cairo_runner/Scarb.lock new file mode 100644 index 0000000..f931558 --- /dev/null +++ b/cairo_runner/Scarb.lock @@ -0,0 +1,13 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "cairo_6" +version = "0.1.0" + +[[package]] +name = "cairo_runner" +version = "0.1.0" +dependencies = [ + "cairo_6", +] diff --git a/cairo_runner/Scarb.toml b/cairo_runner/Scarb.toml new file mode 100644 index 0000000..5a2b88a --- /dev/null +++ b/cairo_runner/Scarb.toml @@ -0,0 +1,13 @@ +[package] +name = "cairo_runner" +version = "0.1.0" +edition = "2025_12" + +[executable] + +[cairo] +enable-gas = false + +[dependencies] +cairo_execute = "2.18.0" +cairo_6 = { path = "../cairo_program" } diff --git a/cairo_runner/src/lib.cairo b/cairo_runner/src/lib.cairo new file mode 100644 index 0000000..83a0460 --- /dev/null +++ b/cairo_runner/src/lib.cairo @@ -0,0 +1,54 @@ +use cairo_6::integer::{add_num, sub_num, multiply_num, divide_num}; + +#[executable] +fn main() { + // === Addition + let a: u32 = 20; + let b: u32 = 7; + match add_num(a, b) { + Result::Ok(val) => println!("Adding {} + {} = {}.", a, b, val), + Result::Err(err) => println!("Adding {} + {} failed: {}.", a, b, err), + } + + // === Subtraction + let (c, d) = (16_u32, 9_u32); + match sub_num(c, d) { + Result::Ok(val) => println!("Subtracting {} - {} = {}.", c, d, val), + Result::Err(err) => println!("Subtracting {} - {} failed: {}.", c, d, err), + } + + // Underflow case + let (e, f) = (3_u32, 10_u32); + match sub_num(e, f) { + Result::Ok(val) => println!("Subtracting {} - {} = {}.", e, f, val), + Result::Err(err) => println!("Subtracting {} - {} failed: {}.", e, f, err), + } + + // === Multiplication + let (g, h) = (5_u32, 6_u32); + match multiply_num(g, h) { + Result::Ok(val) => println!("Multiplying {} * {} = {}.", g, h, val), + Result::Err(err) => println!("Multiplying {} * {} failed: {}.", g, h, err), + } + + // Overflow case + let (i, j) = (4294967295_u32, 2_u32); + match multiply_num(i, j) { + Result::Ok(val) => println!("Multiplying {} * {} = {}.", i, j, val), + Result::Err(err) => println!("Multiplying {} * {} failed: {}.", i, j, err), + } + + // === Division + let (k, l) = (20_u32, 5_u32); + match divide_num(k, l) { + Result::Ok(val) => println!("{} / {} = {}", k, l, val), + Result::Err(err) => println!("{} / {} failed: {}", k, l, err), + } + + // By zero + let (m, n) = (10_u32, 0_u32); + match divide_num(m, n) { + Result::Ok(val) => println!("Dividing {} / {} = {}.", m, n, val), + Result::Err(err) => println!("Dividing {} / {} failed: {}.", m, n, err), + } +} diff --git a/starknet_contracts/Scarb.lock b/starknet_contracts/Scarb.lock index e7e0b2f..93abddb 100644 --- a/starknet_contracts/Scarb.lock +++ b/starknet_contracts/Scarb.lock @@ -1,6 +1,10 @@ # Code generated by scarb DO NOT EDIT. version = 1 +[[package]] +name = "cairo_6" +version = "0.1.0" + [[package]] name = "snforge_scarb_plugin" version = "0.59.0" @@ -20,5 +24,6 @@ dependencies = [ name = "starknet_contracts" version = "0.1.0" dependencies = [ + "cairo_6", "snforge_std", ] diff --git a/starknet_contracts/Scarb.toml b/starknet_contracts/Scarb.toml index 64d28a7..3f8f1c2 100644 --- a/starknet_contracts/Scarb.toml +++ b/starknet_contracts/Scarb.toml @@ -7,6 +7,7 @@ edition = "2024_07" [dependencies] starknet = "2.18.0" +cairo_6 = { path = "../cairo_program" } [dev-dependencies] snforge_std = "0.59.0" diff --git a/starknet_contracts/src/arithmetic.cairo b/starknet_contracts/src/arithmetic.cairo deleted file mode 100644 index 12097fa..0000000 --- a/starknet_contracts/src/arithmetic.cairo +++ /dev/null @@ -1,13 +0,0 @@ -/// Adds two numbers and returns the result. -pub fn add_num(x: u32, y: u32) -> u32 { - x + y -} - -/// Subtracts y from x. Returns an error felt if x < y. -pub fn sub_num(x: u32, y: u32) -> Result { - if x < y { - Result::Err('Subtraction error') - } else { - Result::Ok(x - y) - } -} diff --git a/starknet_contracts/src/lib.cairo b/starknet_contracts/src/lib.cairo index 49ff3b0..cdc749b 100644 --- a/starknet_contracts/src/lib.cairo +++ b/starknet_contracts/src/lib.cairo @@ -14,6 +14,7 @@ pub trait ICounter { pub mod Counter { use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use starknet::{ContractAddress, get_caller_address}; + use cairo_6::integer::{add_num, sub_num}; #[storage] struct Storage { @@ -64,7 +65,7 @@ pub mod Counter { fn increase_count(ref self: ContractState, amount: u32) { self.assert_only_owner(); assert(amount != 0, 'Amount cannot be 0'); - let new_count = self.count.read() + amount; + let new_count = add_num(self.count.read(), amount).expect('Amount causes overflow'); self.count.write(new_count); self.emit(Event::CountIncreased(CountIncreased { amount, new_count })); } @@ -73,8 +74,7 @@ pub mod Counter { self.assert_only_owner(); assert(amount != 0, 'Amount cannot be 0'); let current_count = self.count.read(); - assert(current_count >= amount, 'Amount exceeds count'); - let new_count = current_count - amount; + let new_count = sub_num(current_count, amount).expect('Amount exceeds count'); self.count.write(new_count); self.emit(Event::CountDecreased(CountDecreased { amount, new_count })); } From e1ef21d0f8b2ada180eafb45a57acce9be4e006f Mon Sep 17 00:00:00 2001 From: Olorunshogo Moses BAMTEFA Date: Mon, 11 May 2026 08:37:52 +0100 Subject: [PATCH 4/7] chore: removing cairo_runner --- cairo_runner/Scarb.lock | 13 --------- cairo_runner/Scarb.toml | 13 --------- cairo_runner/src/lib.cairo | 54 -------------------------------------- 3 files changed, 80 deletions(-) delete mode 100644 cairo_runner/Scarb.lock delete mode 100644 cairo_runner/Scarb.toml delete mode 100644 cairo_runner/src/lib.cairo diff --git a/cairo_runner/Scarb.lock b/cairo_runner/Scarb.lock deleted file mode 100644 index f931558..0000000 --- a/cairo_runner/Scarb.lock +++ /dev/null @@ -1,13 +0,0 @@ -# Code generated by scarb DO NOT EDIT. -version = 1 - -[[package]] -name = "cairo_6" -version = "0.1.0" - -[[package]] -name = "cairo_runner" -version = "0.1.0" -dependencies = [ - "cairo_6", -] diff --git a/cairo_runner/Scarb.toml b/cairo_runner/Scarb.toml deleted file mode 100644 index 5a2b88a..0000000 --- a/cairo_runner/Scarb.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "cairo_runner" -version = "0.1.0" -edition = "2025_12" - -[executable] - -[cairo] -enable-gas = false - -[dependencies] -cairo_execute = "2.18.0" -cairo_6 = { path = "../cairo_program" } diff --git a/cairo_runner/src/lib.cairo b/cairo_runner/src/lib.cairo deleted file mode 100644 index 83a0460..0000000 --- a/cairo_runner/src/lib.cairo +++ /dev/null @@ -1,54 +0,0 @@ -use cairo_6::integer::{add_num, sub_num, multiply_num, divide_num}; - -#[executable] -fn main() { - // === Addition - let a: u32 = 20; - let b: u32 = 7; - match add_num(a, b) { - Result::Ok(val) => println!("Adding {} + {} = {}.", a, b, val), - Result::Err(err) => println!("Adding {} + {} failed: {}.", a, b, err), - } - - // === Subtraction - let (c, d) = (16_u32, 9_u32); - match sub_num(c, d) { - Result::Ok(val) => println!("Subtracting {} - {} = {}.", c, d, val), - Result::Err(err) => println!("Subtracting {} - {} failed: {}.", c, d, err), - } - - // Underflow case - let (e, f) = (3_u32, 10_u32); - match sub_num(e, f) { - Result::Ok(val) => println!("Subtracting {} - {} = {}.", e, f, val), - Result::Err(err) => println!("Subtracting {} - {} failed: {}.", e, f, err), - } - - // === Multiplication - let (g, h) = (5_u32, 6_u32); - match multiply_num(g, h) { - Result::Ok(val) => println!("Multiplying {} * {} = {}.", g, h, val), - Result::Err(err) => println!("Multiplying {} * {} failed: {}.", g, h, err), - } - - // Overflow case - let (i, j) = (4294967295_u32, 2_u32); - match multiply_num(i, j) { - Result::Ok(val) => println!("Multiplying {} * {} = {}.", i, j, val), - Result::Err(err) => println!("Multiplying {} * {} failed: {}.", i, j, err), - } - - // === Division - let (k, l) = (20_u32, 5_u32); - match divide_num(k, l) { - Result::Ok(val) => println!("{} / {} = {}", k, l, val), - Result::Err(err) => println!("{} / {} failed: {}", k, l, err), - } - - // By zero - let (m, n) = (10_u32, 0_u32); - match divide_num(m, n) { - Result::Ok(val) => println!("Dividing {} / {} = {}.", m, n, val), - Result::Err(err) => println!("Dividing {} / {} failed: {}.", m, n, err), - } -} From 91ecc9d083eb227efe3c929b75c4d61c7f5f7a94 Mon Sep 17 00:00:00 2001 From: Olorunshogo Moses BAMTEFA Date: Mon, 11 May 2026 08:38:24 +0100 Subject: [PATCH 5/7] feat: adding the starknet contracts --- starknet_contracts/snfoundry.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/starknet_contracts/snfoundry.toml b/starknet_contracts/snfoundry.toml index c06aab7..70bddb2 100644 --- a/starknet_contracts/snfoundry.toml +++ b/starknet_contracts/snfoundry.toml @@ -1,3 +1,5 @@ +[sncast.default] +account = "cairo_account" # Visit https://foundry-rs.github.io/starknet-foundry/appendix/snfoundry-toml.html # and https://foundry-rs.github.io/starknet-foundry/projects/configuration.html for more information From 4d3b24dae832aa26a796c59bf1f0a4bb31e8f742 Mon Sep 17 00:00:00 2001 From: Olorunshogo Moses BAMTEFA Date: Mon, 11 May 2026 08:43:39 +0100 Subject: [PATCH 6/7] chore: formatting the files --- starknet_contracts/src/lib.cairo | 2 +- starknet_contracts/tests/test_counter.cairo | 82 ++++++++++----------- 2 files changed, 40 insertions(+), 44 deletions(-) diff --git a/starknet_contracts/src/lib.cairo b/starknet_contracts/src/lib.cairo index cdc749b..02ba72e 100644 --- a/starknet_contracts/src/lib.cairo +++ b/starknet_contracts/src/lib.cairo @@ -12,9 +12,9 @@ pub trait ICounter { /// Simple contract for managing count with ownership and events. #[starknet::contract] pub mod Counter { + use cairo_6::integer::{add_num, sub_num}; use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use starknet::{ContractAddress, get_caller_address}; - use cairo_6::integer::{add_num, sub_num}; #[storage] struct Storage { diff --git a/starknet_contracts/tests/test_counter.cairo b/starknet_contracts/tests/test_counter.cairo index 6a7c129..5bc3e81 100644 --- a/starknet_contracts/tests/test_counter.cairo +++ b/starknet_contracts/tests/test_counter.cairo @@ -1,11 +1,13 @@ -use starknet::SyscallResultTrait; -use starknet::ContractAddress; use snforge_std::{ - declare, ContractClassTrait, DeclareResultTrait, start_cheat_caller_address, - stop_cheat_caller_address, spy_events, EventSpyAssertionsTrait, + ContractClassTrait, DeclareResultTrait, EventSpyAssertionsTrait, declare, spy_events, + start_cheat_caller_address, stop_cheat_caller_address, +}; +use starknet::{ContractAddress, SyscallResultTrait}; +use starknet_contracts::Counter::{CountDecreased, CountIncreased, CountReset, Event}; +use starknet_contracts::{ + ICounterDispatcher, ICounterDispatcherTrait, ICounterSafeDispatcher, + ICounterSafeDispatcherTrait, }; -use starknet_contracts::{ICounterDispatcher, ICounterDispatcherTrait, ICounterSafeDispatcher, ICounterSafeDispatcherTrait}; -use starknet_contracts::Counter::{CountIncreased, CountDecreased, CountReset, Event}; // === Helpers fn OWNER() -> ContractAddress { @@ -28,7 +30,6 @@ fn deploy_counter() -> (ICounterDispatcher, ICounterSafeDispatcher, ContractAddr } // === Deployment - #[test] fn test_initial_state() { let (dispatcher, _, _) = deploy_counter(); @@ -36,7 +37,7 @@ fn test_initial_state() { assert(dispatcher.get_owner() == OWNER(), 'Owner should match constructor'); } -// === increase_count +// === Increase count #[test] fn test_increase_count() { let (dispatcher, _, contract_address) = deploy_counter(); @@ -66,7 +67,7 @@ fn test_increase_count_zero_amount_fails() { Result::Err(panic_data) => { assert(*panic_data.at(0) == 'Amount cannot be 0', 'Wrong error message'); }, - }; + } stop_cheat_caller_address(contract_address); } @@ -80,12 +81,11 @@ fn test_increase_count_not_owner_fails() { Result::Err(panic_data) => { assert(*panic_data.at(0) == 'Caller is not owner', 'Wrong error message'); }, - }; + } stop_cheat_caller_address(contract_address); } -// === decrease_count - +// === Decrease count #[test] fn test_decrease_count() { let (dispatcher, _, contract_address) = deploy_counter(); @@ -106,7 +106,7 @@ fn test_decrease_count_zero_amount_fails() { Result::Err(panic_data) => { assert(*panic_data.at(0) == 'Amount cannot be 0', 'Wrong error message'); }, - }; + } stop_cheat_caller_address(contract_address); } @@ -121,7 +121,7 @@ fn test_decrease_count_exceeds_fails() { Result::Err(panic_data) => { assert(*panic_data.at(0) == 'Amount exceeds count', 'Wrong error message'); }, - }; + } stop_cheat_caller_address(contract_address); } @@ -135,12 +135,11 @@ fn test_decrease_count_not_owner_fails() { Result::Err(panic_data) => { assert(*panic_data.at(0) == 'Caller is not owner', 'Wrong error message'); }, - }; + } stop_cheat_caller_address(contract_address); } -// === reset_count - +// === Reset count #[test] fn test_reset_count() { let (dispatcher, _, contract_address) = deploy_counter(); @@ -161,12 +160,11 @@ fn test_reset_count_not_owner_fails() { Result::Err(panic_data) => { assert(*panic_data.at(0) == 'Caller is not owner', 'Wrong error message'); }, - }; + } stop_cheat_caller_address(contract_address); } // === Events - #[test] fn test_increase_count_emits_event() { let (dispatcher, _, contract_address) = deploy_counter(); @@ -175,14 +173,15 @@ fn test_increase_count_emits_event() { dispatcher.increase_count(5); stop_cheat_caller_address(contract_address); - spy.assert_emitted( - @array![ - ( - contract_address, - Event::CountIncreased(CountIncreased { amount: 5, new_count: 5 }), - ), - ], - ); + spy + .assert_emitted( + @array![ + ( + contract_address, + Event::CountIncreased(CountIncreased { amount: 5, new_count: 5 }), + ), + ], + ); } #[test] @@ -194,14 +193,15 @@ fn test_decrease_count_emits_event() { dispatcher.decrease_count(3); stop_cheat_caller_address(contract_address); - spy.assert_emitted( - @array![ - ( - contract_address, - Event::CountDecreased(CountDecreased { amount: 3, new_count: 7 }), - ), - ], - ); + spy + .assert_emitted( + @array![ + ( + contract_address, + Event::CountDecreased(CountDecreased { amount: 3, new_count: 7 }), + ), + ], + ); } #[test] @@ -213,12 +213,8 @@ fn test_reset_count_emits_event() { dispatcher.reset_count(); stop_cheat_caller_address(contract_address); - spy.assert_emitted( - @array![ - ( - contract_address, - Event::CountReset(CountReset { previous_count: 8 }), - ), - ], - ); + spy + .assert_emitted( + @array![(contract_address, Event::CountReset(CountReset { previous_count: 8 }))], + ); } From c6850ab9dec54916ba1c664bf1fcbb1d6f19bb3c Mon Sep 17 00:00:00 2001 From: Olorunshogo Moses BAMTEFA Date: Wed, 20 May 2026 08:28:44 +0100 Subject: [PATCH 7/7] chore: styling here and there --- cairo_runner/Scarb.lock | 13 +++++ cairo_runner/Scarb.toml | 13 +++++ cairo_runner/src/lib.cairo | 54 +++++++++++++++++++++ starknet_contracts/tests/test_counter.cairo | 1 + 4 files changed, 81 insertions(+) create mode 100644 cairo_runner/Scarb.lock create mode 100644 cairo_runner/Scarb.toml create mode 100644 cairo_runner/src/lib.cairo diff --git a/cairo_runner/Scarb.lock b/cairo_runner/Scarb.lock new file mode 100644 index 0000000..f931558 --- /dev/null +++ b/cairo_runner/Scarb.lock @@ -0,0 +1,13 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "cairo_6" +version = "0.1.0" + +[[package]] +name = "cairo_runner" +version = "0.1.0" +dependencies = [ + "cairo_6", +] diff --git a/cairo_runner/Scarb.toml b/cairo_runner/Scarb.toml new file mode 100644 index 0000000..5a2b88a --- /dev/null +++ b/cairo_runner/Scarb.toml @@ -0,0 +1,13 @@ +[package] +name = "cairo_runner" +version = "0.1.0" +edition = "2025_12" + +[executable] + +[cairo] +enable-gas = false + +[dependencies] +cairo_execute = "2.18.0" +cairo_6 = { path = "../cairo_program" } diff --git a/cairo_runner/src/lib.cairo b/cairo_runner/src/lib.cairo new file mode 100644 index 0000000..83a0460 --- /dev/null +++ b/cairo_runner/src/lib.cairo @@ -0,0 +1,54 @@ +use cairo_6::integer::{add_num, sub_num, multiply_num, divide_num}; + +#[executable] +fn main() { + // === Addition + let a: u32 = 20; + let b: u32 = 7; + match add_num(a, b) { + Result::Ok(val) => println!("Adding {} + {} = {}.", a, b, val), + Result::Err(err) => println!("Adding {} + {} failed: {}.", a, b, err), + } + + // === Subtraction + let (c, d) = (16_u32, 9_u32); + match sub_num(c, d) { + Result::Ok(val) => println!("Subtracting {} - {} = {}.", c, d, val), + Result::Err(err) => println!("Subtracting {} - {} failed: {}.", c, d, err), + } + + // Underflow case + let (e, f) = (3_u32, 10_u32); + match sub_num(e, f) { + Result::Ok(val) => println!("Subtracting {} - {} = {}.", e, f, val), + Result::Err(err) => println!("Subtracting {} - {} failed: {}.", e, f, err), + } + + // === Multiplication + let (g, h) = (5_u32, 6_u32); + match multiply_num(g, h) { + Result::Ok(val) => println!("Multiplying {} * {} = {}.", g, h, val), + Result::Err(err) => println!("Multiplying {} * {} failed: {}.", g, h, err), + } + + // Overflow case + let (i, j) = (4294967295_u32, 2_u32); + match multiply_num(i, j) { + Result::Ok(val) => println!("Multiplying {} * {} = {}.", i, j, val), + Result::Err(err) => println!("Multiplying {} * {} failed: {}.", i, j, err), + } + + // === Division + let (k, l) = (20_u32, 5_u32); + match divide_num(k, l) { + Result::Ok(val) => println!("{} / {} = {}", k, l, val), + Result::Err(err) => println!("{} / {} failed: {}", k, l, err), + } + + // By zero + let (m, n) = (10_u32, 0_u32); + match divide_num(m, n) { + Result::Ok(val) => println!("Dividing {} / {} = {}.", m, n, val), + Result::Err(err) => println!("Dividing {} / {} failed: {}.", m, n, err), + } +} diff --git a/starknet_contracts/tests/test_counter.cairo b/starknet_contracts/tests/test_counter.cairo index 5bc3e81..2fa2f5d 100644 --- a/starknet_contracts/tests/test_counter.cairo +++ b/starknet_contracts/tests/test_counter.cairo @@ -218,3 +218,4 @@ fn test_reset_count_emits_event() { @array![(contract_address, Event::CountReset(CountReset { previous_count: 8 }))], ); } + \ No newline at end of file