diff --git a/staking_contract/.gitignore b/staking_contract/.gitignore new file mode 100644 index 0000000..4096f8b --- /dev/null +++ b/staking_contract/.gitignore @@ -0,0 +1,5 @@ +target +.snfoundry_cache/ +snfoundry_trace/ +coverage/ +profile/ diff --git a/staking_contract/.tool-versions b/staking_contract/.tool-versions new file mode 100644 index 0000000..8c65c33 --- /dev/null +++ b/staking_contract/.tool-versions @@ -0,0 +1 @@ +scarb 2.11.4 diff --git a/staking_contract/Scarb.lock b/staking_contract/Scarb.lock new file mode 100644 index 0000000..d08760f --- /dev/null +++ b/staking_contract/Scarb.lock @@ -0,0 +1,14 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "snforge_std" +version = "0.22.0" +source = "git+https://github.com/foundry-rs/starknet-foundry?tag=v0.22.0#9b215944c6c5871c738381b4ded61bbf06e7ba35" + +[[package]] +name = "staking_contract" +version = "0.1.0" +dependencies = [ + "snforge_std", +] diff --git a/staking_contract/Scarb.toml b/staking_contract/Scarb.toml new file mode 100644 index 0000000..1925eee --- /dev/null +++ b/staking_contract/Scarb.toml @@ -0,0 +1,17 @@ +[package] +name = "staking_contract" +version = "0.1.0" +edition = "2024_07" + +[dependencies] +starknet = "2.11.4" + +[dev-dependencies] +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", rev = "f994c8b" } +assert_macros = "2.11.4" + +[[target.starknet-contract]] +sierra = true + +[scripts] +test = "snforge test" diff --git a/staking_contract/scripts/deploy.cairo b/staking_contract/scripts/deploy.cairo new file mode 100644 index 0000000..e69de29 diff --git a/staking_contract/snfoundry.toml b/staking_contract/snfoundry.toml new file mode 100644 index 0000000..d194996 --- /dev/null +++ b/staking_contract/snfoundry.toml @@ -0,0 +1,11 @@ +# 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 + +# [sncast.default] # Define a profile name +# url = "https://starknet-sepolia.public.blastapi.io/rpc/v0_9" # Url of the RPC provider +# accounts-file = "../account-file" # Path to the file with the account data +# account = "mainuser" # Account from `accounts_file` or default account file that will be used for the transactions +# keystore = "~/keystore" # Path to the keystore file +# wait-params = { timeout = 300, retry-interval = 10 } # Wait for submitted transaction parameters +# block-explorer = "StarkScan" # Block explorer service used to display links to transaction details +# show-explorer-links = true # Print links pointing to pages with transaction details in the chosen block explorer diff --git a/staking_contract/src/lib.cairo b/staking_contract/src/lib.cairo new file mode 100644 index 0000000..0ce0aae --- /dev/null +++ b/staking_contract/src/lib.cairo @@ -0,0 +1,3 @@ +mod staking; +mod stark_token; +mod reward_token; diff --git a/staking_contract/src/reward_token.cairo b/staking_contract/src/reward_token.cairo new file mode 100644 index 0000000..c9fbad6 --- /dev/null +++ b/staking_contract/src/reward_token.cairo @@ -0,0 +1,78 @@ +// reward_token.cairo +%lang starknet + +use starknet::ContractAddress; +use core::integer::u256; +use starknet::storage::LegacyMap; +use starknet::event; + +#[storage] +struct Storage { + balances: LegacyMap, + total_supply: u256, + name: felt252, + symbol: felt252 +} + +#[event] +struct Transfer { + from: ContractAddress, + to: ContractAddress, + value: u256 +} + +#[contract] +mod RewardToken { + use super::{Storage, Transfer}; + use starknet::ContractAddress; + use core::integer::u256; + + #[constructor] + fn constructor( + ref self: Storage, + name: felt252, + symbol: felt252, + initial_supply: u256, + owner: ContractAddress + ) { + self.name = name; + self.symbol = symbol; + self.total_supply = initial_supply; + self.balances.write(owner, initial_supply); + emit!(Transfer { from: ContractAddress::from(0), to: owner, value: initial_supply }); + } + + #[external(v0)] + fn name(self: @Storage) -> felt252 { + self.name + } + + #[external(v0)] + fn symbol(self: @Storage) -> felt252 { + self.symbol + } + + #[external(v0)] + fn totalSupply(self: @Storage) -> u256 { + self.total_supply + } + + #[external(v0)] + fn balanceOf(self: @Storage, owner: ContractAddress) -> u256 { + self.balances.read(owner) + } + + #[external(v0)] + fn transfer(ref self: Storage, to: ContractAddress, amount: u256) { + let sender = starknet::get_caller_address(); + let sender_balance = self.balances.read(sender); + assert(sender_balance >= amount, 'Insufficient balance'); + + self.balances.write(sender, sender_balance - amount); + + let receiver_balance = self.balances.read(to); + self.balances.write(to, receiver_balance + amount); + + emit!(Transfer { from: sender, to, value: amount }); + } +} diff --git a/staking_contract/src/staking.cairo b/staking_contract/src/staking.cairo new file mode 100644 index 0000000..d8d086f --- /dev/null +++ b/staking_contract/src/staking.cairo @@ -0,0 +1,93 @@ +%lang starknet + +use starknet::ContractAddress; +use core::integer::u256; +use starknet::storage::LegacyMap; + +#[storage] +struct Storage { + staking_token: ContractAddress, + reward_token: ContractAddress, + stakes: LegacyMap, + rewards: LegacyMap, +} + +#[contract] +mod StakingContract { + use super::{Storage}; + use starknet::ContractAddress; + use core::integer::u256; + + // -------------------------- + // Constructor + // -------------------------- + #[constructor] + fn constructor( + ref self: Storage, + staking_token: ContractAddress, + reward_token: ContractAddress + ) { + self.staking_token = staking_token; + self.reward_token = reward_token; + } + + // -------------------------- + // Public Functions + // -------------------------- + + /// Stake tokens (user should have approved this contract beforehand if needed). + #[external(v0)] + fn stake(ref self: Storage, amount: u256) { + let caller = starknet::get_caller_address(); + assert(amount > 0, 'Amount must be > 0'); + + // Update stake balance + let current_stake = self.stakes.read(caller); + self.stakes.write(caller, current_stake + amount); + + // For simplicity, reward = 10% of staked amount + let reward = amount / 10; + let current_reward = self.rewards.read(caller); + self.rewards.write(caller, current_reward + reward); + } + + /// Withdraw staked tokens + #[external(v0)] + fn withdraw(ref self: Storage, amount: u256) { + let caller = starknet::get_caller_address(); + let current_stake = self.stakes.read(caller); + assert(current_stake >= amount, 'Not enough staked'); + + self.stakes.write(caller, current_stake - amount); + } + + /// Claim rewards (distributes RewardToken) + #[external(v0)] + fn claim_rewards(ref self: Storage) { + let caller = starknet::get_caller_address(); + let reward_amount = self.rewards.read(caller); + assert(reward_amount > 0, 'No rewards available'); + + // Reset rewards before transferring + self.rewards.write(caller, 0); + + // Call into RewardToken contract to transfer rewards + starknet::call_contract_syscall( + self.reward_token, + 'transfer', + (caller, reward_amount) + ).unwrap(); + } + + /// View: check stake balance + #[external(v0)] + fn get_stake(self: @Storage, user: ContractAddress) -> u256 { + self.stakes.read(user) + } + + /// View: check pending rewards + #[external(v0)] + fn get_rewards(self: @Storage, user: ContractAddress) -> u256 { + self.rewards.read(user) + } +} diff --git a/staking_contract/src/stark_token.cairo b/staking_contract/src/stark_token.cairo new file mode 100644 index 0000000..da75fbf --- /dev/null +++ b/staking_contract/src/stark_token.cairo @@ -0,0 +1,72 @@ +// stark_token.cairo +%lang starknet + +use starknet::ContractAddress; +use core::integer::u256; +use starknet::storage::LegacyMap; +use starknet::event; + +#[storage] +struct Storage { + balances: LegacyMap, + total_supply: u256, + name: felt252, + symbol: felt252 +} + +#[event] +struct Transfer { + from: ContractAddress, + to: ContractAddress, + value: u256 +} + +#[contract] +mod StarkToken { + use super::{Storage, Transfer}; + use starknet::ContractAddress; + use core::integer::u256; + + #[constructor] + fn constructor(ref self: Storage, name: felt252, symbol: felt252, initial_supply: u256, owner: ContractAddress) { + self.name = name; + self.symbol = symbol; + self.total_supply = initial_supply; + self.balances.write(owner, initial_supply); + emit!(Transfer { from: ContractAddress::from(0), to: owner, value: initial_supply }); + } + + #[external(v0)] + fn name(self: @Storage) -> felt252 { + self.name + } + + #[external(v0)] + fn symbol(self: @Storage) -> felt252 { + self.symbol + } + + #[external(v0)] + fn totalSupply(self: @Storage) -> u256 { + self.total_supply + } + + #[external(v0)] + fn balanceOf(self: @Storage, owner: ContractAddress) -> u256 { + self.balances.read(owner) + } + + #[external(v0)] + fn transfer(ref self: Storage, to: ContractAddress, amount: u256) { + let sender = starknet::get_caller_address(); + let sender_balance = self.balances.read(sender); + assert(sender_balance >= amount, 'Insufficient balance'); + + self.balances.write(sender, sender_balance - amount); + + let receiver_balance = self.balances.read(to); + self.balances.write(to, receiver_balance + amount); + + emit!(Transfer { from: sender, to, value: amount }); + } +} diff --git a/staking_contract/tests/test_contract.cairo b/staking_contract/tests/test_contract.cairo new file mode 100644 index 0000000..498c1d8 --- /dev/null +++ b/staking_contract/tests/test_contract.cairo @@ -0,0 +1,47 @@ +use starknet::ContractAddress; + +use snforge_std_deprecated::{declare, ContractClassTrait, DeclareResultTrait}; + +use staking_contract::IHelloStarknetSafeDispatcher; +use staking_contract::IHelloStarknetSafeDispatcherTrait; +use staking_contract::IHelloStarknetDispatcher; +use staking_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)); + } + }; +} diff --git a/starknet_contracts/.tool-versions b/starknet_contracts/.tool-versions new file mode 100644 index 0000000..8c65c33 --- /dev/null +++ b/starknet_contracts/.tool-versions @@ -0,0 +1 @@ +scarb 2.11.4 diff --git a/starknet_contracts/Scarb.lock b/starknet_contracts/Scarb.lock index fcb24ad..ec780ec 100644 --- a/starknet_contracts/Scarb.lock +++ b/starknet_contracts/Scarb.lock @@ -17,7 +17,7 @@ dependencies = [ ] [[package]] -name = "Starknet_contracts" +name = "starknet_contracts" version = "0.1.0" dependencies = [ "snforge_std", diff --git a/starknet_contracts/Scarb.toml b/starknet_contracts/Scarb.toml index 361c44a..2d8de69 100644 --- a/starknet_contracts/Scarb.toml +++ b/starknet_contracts/Scarb.toml @@ -1,18 +1,18 @@ [package] -name = "Starknet_contracts" +name = "starknet_contracts" version = "0.1.0" edition = "2024_07" # See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html [dependencies] -Starknet = "2.11.4" +starknet = "2.11.4" [dev-dependencies] snforge_std = "0.43.1" assert_macros = "2.11.4" -[[target.Starknet-contract]] +[[target.starknet-contract]] sierra = true [scripts] diff --git a/starknet_contracts/src/contracts/HelloStarknet.cairo b/starknet_contracts/src/contracts/HelloStarknet.cairo index a12d4a9..5c6d630 100644 --- a/starknet_contracts/src/contracts/HelloStarknet.cairo +++ b/starknet_contracts/src/contracts/HelloStarknet.cairo @@ -1,11 +1,8 @@ -/// Simple contract for managing balance. -#[Starknet::contract] +#[starknet::contract] pub mod HelloStarknet { - - use Starknet_contracts::interfaces::IHelloStarknet::IHelloStarknet; - // use Starknet::storage::{StoragePointerReadAccess, StoragePathEntry, StoragePointerWriteAccess, Map }; - use Starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, StoragePointerWriteAccess }; - use Starknet::{ContractAddress, get_caller_address}; + use starknet::storage::*; + use starknet::{ContractAddress, get_caller_address}; + use crate::interfaces::IHelloStarknet::IHelloStarknet; // <--- correct import #[storage] struct Storage { @@ -14,15 +11,16 @@ pub mod HelloStarknet { } #[event] - #[derive(Drop, Starknet::Event)] + #[derive(Drop, starknet::Event)] pub enum Event { - Balance : BalanceIncreased, + Balance: BalanceUpdated, } - #[derive(Drop, Starknet::Event)] - pub struct BalanceIncreased { + #[derive(Drop, starknet::Event)] + pub struct BalanceUpdated { pub caller: ContractAddress, - pub amount: felt252, + pub old_amount: felt252, + pub new_amount: felt252, } #[abi(embed_v0)] @@ -31,22 +29,49 @@ pub mod HelloStarknet { assert(amount != 0, 'Amount cannot be 0'); let caller = get_caller_address(); - let updated_amount = self.balance.read() + amount; - self.balance.write(updated_amount); + let old_total = self.balance.read(); + let new_total = old_total + amount; + self.balance.write(new_total); - // let unique_balance = self.balances.entry(caller).read(); + let old_unique = self.balances.read(caller); + let new_unique = old_unique + amount; + self.balances.write(caller, new_unique); - let unique_balance = self.balances.read(caller); - // self.balances.entry(caller).write(unique_balance + amount); - self.balances.write(caller, unique_balance + amount); - - // self.balance.write(self.balance.read() + amount); - - self.emit(BalanceIncreased{caller, amount}); + self.emit(Event::Balance(BalanceUpdated { caller, old_amount: old_unique, new_amount: new_unique })); } fn get_balance(self: @ContractState) -> felt252 { self.balance.read() } + + fn get_unique_balance(self: @ContractState, addr: ContractAddress) -> felt252 { + self.balances.read(addr) + } + + fn set_balance(ref self: ContractState, amount: felt252) { + assert(amount != 0, 'Amount cannot be 0'); + let caller = get_caller_address(); + + let old_unique = self.balances.read(caller); + self.balances.write(caller, amount); + + let old_total = self.balance.read(); + let new_total = old_total + amount - old_unique; + self.balance.write(new_total); + + self.emit(Event::Balance(BalanceUpdated { caller, old_amount: old_unique, new_amount: amount })); + } + + fn reset_balance(ref self: ContractState) { + let caller = get_caller_address(); + let old_unique = self.balances.read(caller); + + let total = self.balance.read(); + self.balance.write(total - old_unique); + + self.balances.write(caller, 0); + + self.emit(Event::Balance(BalanceUpdated { caller, old_amount: old_unique, new_amount: 0 })); + } } -} \ No newline at end of file +} diff --git a/starknet_contracts/src/contracts/counter.cairo b/starknet_contracts/src/contracts/counter.cairo index a4828e4..176d352 100644 --- a/starknet_contracts/src/contracts/counter.cairo +++ b/starknet_contracts/src/contracts/counter.cairo @@ -1,51 +1,51 @@ -#[Starknet::contract] -pub mod Counter { - // use Starknet::ContractAddress; - // use Starknet::get_caller_address; - use Starknet_contracts::interfaces::ICounter::ICounter; - use Starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; +// #[Starknet::contract] +// pub mod Counter { + // use starknet::ContractAddress; + // use starknet::get_caller_address; + // use starknet_contracts::interfaces::ICounter::ICounter; + // use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; - #[storage] - struct Storage { - count: u32, - } + // #[storage] + // struct Storage { + // count: u32, + // } - #[event] - #[derive(Drop, Starknet::Event)] - pub enum Event { - CountUpdated : CountUpdated, - } + // #[event] + // #[derive(Drop, Starknet::Event)] + // pub enum Event { + // CountUpdated : CountUpdated, + // } - #[derive(Drop, Starknet::Event)] - struct CountUpdated { - old_value: u32, - new_value: u32, - } + // #[derive(Drop, Starknet::Event)] + // struct CountUpdated { + // old_value: u32, + // new_value: u32, + // } - #[constructor] - fn constructor(ref self: ContractState) { - self.count.write(0); - } + // #[constructor] + // fn constructor(ref self: ContractState) { + // self.count.write(0); + // } - #[abi(embed_v0)] - impl CounterImpl of ICounter { - fn get_count(self: @ContractState) -> u32 { - self.count.read() - } + // #[abi(embed_v0)] + // impl CounterImpl of ICounter { + // fn get_count(self: @ContractState) -> u32 { + // self.count.read() + // } - fn increment(ref self: ContractState) { - let old_value = self.count.read(); - let new_value = old_value + 1; - self.count.write(new_value); - self.emit(CountUpdated { old_value, new_value }); - } + // fn increment(ref self: ContractState) { + // let old_value = self.count.read(); + // let new_value = old_value + 1; + // self.count.write(new_value); + // self.emit(CountUpdated { old_value, new_value }); + // } - fn decrement(ref self: ContractState) { - let old_value = self.count.read(); - assert(old_value > 0, 'Count cannot be negative'); - let new_value = old_value - 1; - self.count.write(new_value); - self.emit(CountUpdated { old_value, new_value }); - } - } -} \ No newline at end of file + // fn decrement(ref self: ContractState) { + // let old_value = self.count.read(); + // assert(old_value > 0, 'Count cannot be negative'); +// let new_value = old_value - 1; +// self.count.write(new_value); + // self.emit(CountUpdated { old_value, new_value }); +// } +// } +// } \ No newline at end of file diff --git a/starknet_contracts/src/interfaces/ICounter.cairo b/starknet_contracts/src/interfaces/ICounter.cairo index b5d41f5..fab0b2a 100644 --- a/starknet_contracts/src/interfaces/ICounter.cairo +++ b/starknet_contracts/src/interfaces/ICounter.cairo @@ -1,6 +1,6 @@ -#[Starknet::interface] -pub trait ICounter { - fn get_count(self: @TContractState) -> u32; - fn increment(ref self: TContractState); - fn decrement(ref self: TContractState); -} \ No newline at end of file +// #[Starknet::interface] +// pub trait ICounter { + // fn get_count(self: @TContractState) -> u32; + // fn increment(ref self: TContractState); + // fn decrement(ref self: TContractState); +// } \ No newline at end of file diff --git a/starknet_contracts/src/interfaces/IHelloStarknet.cairo b/starknet_contracts/src/interfaces/IHelloStarknet.cairo index 92ed438..2cbf017 100644 --- a/starknet_contracts/src/interfaces/IHelloStarknet.cairo +++ b/starknet_contracts/src/interfaces/IHelloStarknet.cairo @@ -1,9 +1,11 @@ +use starknet::ContractAddress; // <--- this was missing + /// Interface representing `HelloContract`. -/// This interface allows modification and retrieval of the contract balance. -#[Starknet::interface] +#[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; -} \ No newline at end of file + fn get_unique_balance(self: @TContractState, addr: ContractAddress) -> felt252; + fn set_balance(ref self: TContractState, amount: felt252); + fn reset_balance(ref self: TContractState); +} diff --git a/tasks/Task-5-b/screenshots/deployment.png b/tasks/Task-5-b/screenshots/deployment.png new file mode 100644 index 0000000..e69de29 diff --git a/tasks/Task-5-b/screenshots/get_count.png b/tasks/Task-5-b/screenshots/get_count.png new file mode 100644 index 0000000..e69de29 diff --git a/tasks/Task-5-b/screenshots/increment1.png b/tasks/Task-5-b/screenshots/increment1.png new file mode 100644 index 0000000..e69de29 diff --git a/tasks/Task-5-b/screenshots/increment2.png b/tasks/Task-5-b/screenshots/increment2.png new file mode 100644 index 0000000..e69de29 diff --git a/tasks/Task-5-b/sncast.md b/tasks/Task-5-b/sncast.md new file mode 100644 index 0000000..b10c8a1 --- /dev/null +++ b/tasks/Task-5-b/sncast.md @@ -0,0 +1,13 @@ +![Contract Deployment](./screenshots/deployment.png) + +![Call Function](./screenshots/deployment.png) + +![Increment1](./screenshots/increment.png) + +![Increment2](./screenshots/increment2.png) + +![Get Count1](./screenshots/get_count.png) + +![Decrement](./screenshots/get_count.png) + +![Get Count2](./screenshots/get_count.png) diff --git a/tasks/session-5-task-a.md b/tasks/session-5-task-a.md index 3711cb5..ba97233 100644 --- a/tasks/session-5-task-a.md +++ b/tasks/session-5-task-a.md @@ -64,6 +64,9 @@ sncast invoke \ --arguments ', 10000000000000000000' ``` +#### MY TRANSACTION HASH +https://sepolia.starkscan.co/tx/0x04178df11f3e300623d54366db9afe7b5c10cdd1f739a64529fe710251c59ca0 + ##### Submission