diff --git a/README.md b/README.md index ece307b..8d2b394 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,177 @@ -# Cairo Bootcamp 6.0 +# StarkNet Restricted ERC-20 Token -Welcome to **Cairo Bootcamp 6** — a hands-on, developer-focused program designed to help you learn Cairo from first principles and build real-world applications. This repository contains learning materials, examples, and exercises used throughout the bootcamp. +A secure ERC-20 token implementation on StarkNet with administrative controls, transfer restrictions, and burn functionality. ---- +* **File-path:** `yahia008/blockheader_test` +* **File-path:** `yahia008/assignment_1` +* **File-path:** `yahia008/assignment_2` +* **File-path:** `yahia008/av2_pro` + +## Overview -## About the Bootcamp +This contract extends standard ERC-20 behavior with admin-managed safeguards. It is designed for cases where token movement must follow configurable rules, while still supporting the usual transfer and allowance flows. -This Cairo Bootcamp is designed to: -- Introduce developers to Cairo -- Teach provable computation fundamentals** -- Explore Starknet and smart contract development -- Encourage hands-on building and experimentation +## Features +### Core ERC-20 Functionality ---- +- Standard token operations: `transfer`, `approve`, and `transfer_from` +- Balance tracking and allowance management +- Token metadata: `name`, `symbol`, `decimals`, and `total_supply` -## 📁 Repository Structure -. -├── cairo_programs/ # Pure Cairo programs (stateless logic) -├── starknet_contracts/ # Smart contracts (stateful logic) -└── README.md \ No newline at end of file +### Admin Controls + +- Transfer limit management: the admin can update the maximum transfer amount +- Global transfer toggle: the admin can disable or re-enable all transfers +- Burn mechanism: the admin can burn tokens from any address +- Spender revocation: the admin can revoke specific spenders from using allowances + +### Security Features + +- Maximum transfer limit enforcement +- Zero-address validation +- Insufficient balance checks +- Allowance validation +- Revoked spender tracking +- Admin-only privileged functions + +## Contract Architecture + +### Interfaces + +#### `IERC20` + +Standard ERC-20 interface with the required functions: + +- `get_name()` +- `get_symbol()` +- `get_decimals()` +- `get_total_supply()` +- `balance_of()` +- `allowance()` +- `transfer()` +- `transfer_from()` +- `approve()` +- `increase_allowance()` +- `decrease_allowance()` + +#### `IAdminRestricted` + +Admin-specific functions: + +- `set_transfer_limit()` - Updates the maximum transfer amount +- `burn()` - Burns tokens from any account +- `revoke_transfers()` - Disables all transfers +- `enable_transfers()` - Re-enables transfers +- `admin_revoke_spender()` - Revokes a specific spender + +### Storage Variables + +```rust +name: felt252 +symbol: felt252 +decimals: u8 +total_supply: u256 +balances: Map +allowances: Map<(ContractAddress, ContractAddress), u256> +revoked_user: Map +admin: ContractAddress +transfer_limit: u256 +transfers_enabled: bool +``` + +### Events + +- `Transfer` - Emitted on token transfers +- `Approval` - Emitted on allowance changes +- `TransferLimitUpdated` - Emitted when the admin updates the transfer limit +- `TransfersRevoked` / `TransfersEnabled` - Emitted when global transfer status changes +- `Burn` - Emitted when tokens are burned + +## Deployment + +### Constructor Parameters + +```rust +fn constructor( + recipient: ContractAddress, + name: felt252, + decimals: u8, + initial_supply: u256, + symbol: felt252, + admin: ContractAddress +) +``` + +Parameter summary: + +- `recipient` - Initial token recipient +- `name` - Token name +- `decimals` - Token decimals +- `initial_supply` - Initial amount to mint +- `symbol` - Token symbol +- `admin` - Admin address + +### Example Deployment + +```cairo +let token = erc20::constructor( + recipient: 0x123..., + name: 'Restricted Token', + decimals: 18, + initial_supply: 1000000_u256, + symbol: 'RST', + admin: 0x456... +); +``` + +## Usage Examples + +### Transfer Validation + +The following transfer will fail if: + +- The amount exceeds `transfer_limit` +- The sender has insufficient balance +- Global transfers are disabled +- A zero address is used + +```cairo +token.transfer(recipient, 5000_u256); // Works +token.transfer(recipient, 15000_u256); // Fails: exceeds limit +``` + +### Admin Operations + +```cairo +// Update transfer limit +token.set_transfer_limit(20000_u256); + +// Burn tokens from any address +token.burn(account_address, 1000_u256); + +// Emergency stop all transfers +token.revoke_transfers(); +token.transfer(recipient, 1000_u256); // Fails: transfers disabled + +// Re-enable transfers +token.enable_transfers(); + +// Revoke a specific spender +token.admin_revoke_spender(owner, spender); +``` + +## Error Codes + +| Error | Description | +| --- | --- | +| `ERC20: not admin` | Caller is not the contract admin | +| `ERC20: transfers disabled` | Global transfers are currently disabled | +| `ERC20: exceeds max limit` | Transfer amount exceeds the current limit | +| `ERC20: insufficient balance` | Sender has insufficient tokens | +| `ERC20: insufficient allowance` | Insufficient allowance for `transfer_from` | +| `ERC20: spender revoked` | Spender has been revoked by the admin | +| `ERC20: limit must be >0` | Transfer limit must be positive | +| `ERC20: zero address` | Zero address is not allowed for the operation | +| `ERC20: approve to 0` | Cannot approve the zero address | +| `ERC20: burn amount >0` | Burn amount must be positive | diff --git a/starknet-agentic b/starknet-agentic new file mode 160000 index 0000000..79d018c --- /dev/null +++ b/starknet-agentic @@ -0,0 +1 @@ +Subproject commit 79d018c1502b58345bc4c867ef15f80419af50c0 diff --git a/starknet.md b/starknet.md new file mode 100644 index 0000000..6496936 --- /dev/null +++ b/starknet.md @@ -0,0 +1,48 @@ +# StarkNet MCP Transfer Agent + +An autonomous multi-step agent workflow built on StarkNet-agentic for intelligent, condition-based token transfers with monitoring and alerting. + +## Objective + +Build a composable autonomous transfer agent using an MCP (Model Context Protocol) server architecture that can: + +- Fetch wallet balances +- Validate transfer conditions +- Execute conditional transfers +- Chain multiple agent calls +- Log execution results +- Trigger alerts on low balance + +## Architecture + +The workflow follows this sequence: + +1. Fetch balance +2. Validate transfer conditions +3. Execute transfer +4. Call another agent if needed +5. Log the result +6. Trigger an alert when balances are low + +## Components + +### MCP Server Structure + +```typescript +const server = new McpServer({ + name: "starknet-transfer-agent", + version: "1.0.0" +}); +``` + +### Available Tools + +| Tool | Description | Parameters | +| --- | --- | --- | +| `fetch_balance` | Get wallet balance | `wallet_address` | +| `validate_transfer` | Check transfer conditions | `amount`, `balance`, `threshold` | +| `execute_transfer` | Perform token transfer | `to`, `amount`, `token_address` | +| `call_agent` | Invoke another agent | `agent_name`, `params` | +| `log_result` | Record execution results | `status`, `details`, `timestamp` | +| `check_threshold` | Monitor balance threshold | `balance`, `threshold` | +| `trigger_alert` | Send a low-balance notification | `wallet`, `current_balance` | diff --git a/yahia008/assignment_1/.gitignore b/yahia008/assignment_1/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/yahia008/assignment_1/.gitignore @@ -0,0 +1 @@ +target diff --git a/yahia008/assignment_1/Scarb.lock b/yahia008/assignment_1/Scarb.lock new file mode 100644 index 0000000..560210b --- /dev/null +++ b/yahia008/assignment_1/Scarb.lock @@ -0,0 +1,6 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "assignment_1" +version = "0.1.0" diff --git a/yahia008/assignment_1/Scarb.toml b/yahia008/assignment_1/Scarb.toml new file mode 100644 index 0000000..1b8c858 --- /dev/null +++ b/yahia008/assignment_1/Scarb.toml @@ -0,0 +1,14 @@ +[package] +name = "assignment_1" +version = "0.1.0" +edition = "2025_12" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[executable] + +[cairo] +enable-gas = false + +[dependencies] +cairo_execute = "2.17.0" diff --git a/yahia008/assignment_1/src/assignment1.cairo b/yahia008/assignment_1/src/assignment1.cairo new file mode 100644 index 0000000..12e2a8c --- /dev/null +++ b/yahia008/assignment_1/src/assignment1.cairo @@ -0,0 +1,74 @@ +use core::num::traits::CheckedMul; +use core::num::traits::CheckedAdd; +use core::num::traits::CheckedSub; + +#[derive(Drop)] +enum MathError { + Overflow, + DivisionByZero, + Underflow, +} + + +#[executable] +fn main() { + let add_result = add_number(0, 0); + match add_result { + Result::Ok(v) => { + assert!(v==0, "assertion failed") + println!("add value {}", v) + }, + Result::Err(_) => println!("add error ", ), + }; + + let mul_result = multiply_number(10, 5); + match mul_result { + Result::Ok(v) => { + assert!(v==50, "assertion failed") + println!("mul value {}", v) + }, + Result::Err(_) => println!("mul error"), + }; + + + let div_result = divide(10, 2); + match div_result { + Result::Ok(v) => { + assert!(v==5, "assertion failed") + println!("div value {}", v) + }, + Result::Err(_) => println!("div error", ), + }; + + let sub_result = sub_num(10, 7); + match sub_result { + Result::Ok(v) => { + assert!(v==3, "assertion failed") + println!("sub value {}", v) + }, + Result::Err(_) => println!("sub error"), + }; +} + + + +fn add_number(x: u128, y: u128) -> Result { + x.checked_add(y).ok_or(MathError::Overflow) +} + +fn multiply_number(x:u128, y:u128) -> Result { + x.checked_mul(y).ok_or(MathError::Overflow) +} + +fn divide(x: u128, y: u128) -> Result{ + if y == 0 { + return Result::Err(MathError::DivisionByZero); + } + + Result::Ok(x / y) +} + +fn sub_num(x:u128, y:u128) -> Result{ + x.checked_sub(y).ok_or(MathError::Underflow) +} + diff --git a/yahia008/assignment_1/src/lib.cairo b/yahia008/assignment_1/src/lib.cairo new file mode 100644 index 0000000..e45ed60 --- /dev/null +++ b/yahia008/assignment_1/src/lib.cairo @@ -0,0 +1 @@ +mod assignment1; diff --git a/yahia008/assignment_2/.gitignore b/yahia008/assignment_2/.gitignore new file mode 100644 index 0000000..4096f8b --- /dev/null +++ b/yahia008/assignment_2/.gitignore @@ -0,0 +1,5 @@ +target +.snfoundry_cache/ +snfoundry_trace/ +coverage/ +profile/ diff --git a/yahia008/assignment_2/Scarb.lock b/yahia008/assignment_2/Scarb.lock new file mode 100644 index 0000000..7c5aa84 --- /dev/null +++ b/yahia008/assignment_2/Scarb.lock @@ -0,0 +1,24 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "assignment_2" +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/yahia008/assignment_2/Scarb.toml b/yahia008/assignment_2/Scarb.toml new file mode 100644 index 0000000..4eb7af4 --- /dev/null +++ b/yahia008/assignment_2/Scarb.toml @@ -0,0 +1,52 @@ +[package] +name = "assignment_2" +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.17.0" + +[dev-dependencies] +snforge_std = "0.59.0" +assert_macros = "2.17.0" + +[[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/yahia008/assignment_2/snfoundry.toml b/yahia008/assignment_2/snfoundry.toml new file mode 100644 index 0000000..686c2ab --- /dev/null +++ b/yahia008/assignment_2/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://api.zan.top/public/starknet-sepolia/rpc/v0_10" # 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 = "Voyager" # 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/yahia008/assignment_2/src/lib.cairo b/yahia008/assignment_2/src/lib.cairo new file mode 100644 index 0000000..f177898 --- /dev/null +++ b/yahia008/assignment_2/src/lib.cairo @@ -0,0 +1,77 @@ +mod mylogic; +use starknet::{ContractAddress}; +#[starknet::interface] +pub trait ICounter { + fn get_count(self: @T) -> u128; + fn get_owner(self: @T) -> ContractAddress; + fn transfer_ownership(ref self: T, new_owner: ContractAddress); + fn increase_count(ref self: T, amount: u128); + fn decrease_count (ref self:T, amount:u128); + fn reset_count(ref self:T); + + + +} + +#[starknet::contract] +mod CounterV2{ + use crate::mylogic; +use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + + use super::ICounter; + use starknet::ContractAddress; + use starknet::get_caller_address; + + + #[storage] + struct Storage{ + count:u128, + owner:ContractAddress + } + + #[constructor] + fn constructor(ref self:ContractState, initial_owner:ContractAddress){ + self.owner.write(initial_owner); + self.count.write(0); + } + + #[abi(embed_v0)] + impl ICounterimpl of ICounter { + + fn get_count(self: @ContractState) -> u128 { + self.count.read() + } + fn get_owner(self: @ContractState) -> ContractAddress{ + self.owner.read() + } + fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress){ + let caller = get_caller_address(); + assert!(caller == self.owner.read(), "Only owner can transfer"); + self.owner.write(new_owner); + } + fn increase_count(ref self: ContractState, amount: u128){ + let caller:ContractAddress = get_caller_address(); + assert!(caller == self.owner.read(), "Only owner can change state"); + + let current = self.count.read(); + let new:u128 = mylogic::increase_count(current, amount); + self.count.write(new); + } + fn decrease_count (ref self:ContractState, amount:u128){ + let caller:ContractAddress = get_caller_address(); + assert!(caller == self.owner.read(), "Only owner can change state"); + + let current = self.count.read(); + let new:u128 = mylogic::decrease_count(current, amount); + self.count.write(new); + } + fn reset_count(ref self:ContractState){ + let caller = get_caller_address(); + assert!(caller == self.owner.read(), "Only owner can change state"); + let new:u128 = mylogic::reset_count(); + self.count.write(new); + + + } + } +} \ No newline at end of file diff --git a/yahia008/assignment_2/src/mylogic.cairo b/yahia008/assignment_2/src/mylogic.cairo new file mode 100644 index 0000000..e716f0a --- /dev/null +++ b/yahia008/assignment_2/src/mylogic.cairo @@ -0,0 +1,24 @@ +use core::num::traits::CheckedAdd; +use core::num::traits::CheckedSub; + + + +pub fn increase_count(current: u128, amount: u128) -> u128{ + if amount == 0 + { + return current; + } + current.checked_add(amount).expect('overflow') +} + +pub fn decrease_count(current: u128, amount: u128) -> u128 { + if amount == 0 + { + return current; + } + current.checked_sub(amount).expect('underflow') +} + +pub fn reset_count() -> u128 { + 0 +} \ No newline at end of file diff --git a/yahia008/av2_pro/arithmetic_logic/.gitignore b/yahia008/av2_pro/arithmetic_logic/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/yahia008/av2_pro/arithmetic_logic/.gitignore @@ -0,0 +1 @@ +target diff --git a/yahia008/av2_pro/arithmetic_logic/Scarb.lock b/yahia008/av2_pro/arithmetic_logic/Scarb.lock new file mode 100644 index 0000000..f50718d --- /dev/null +++ b/yahia008/av2_pro/arithmetic_logic/Scarb.lock @@ -0,0 +1,6 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "arithmetic_logic" +version = "0.1.0" diff --git a/yahia008/av2_pro/arithmetic_logic/Scarb.toml b/yahia008/av2_pro/arithmetic_logic/Scarb.toml new file mode 100644 index 0000000..6e598c9 --- /dev/null +++ b/yahia008/av2_pro/arithmetic_logic/Scarb.toml @@ -0,0 +1,19 @@ +[package] +name = "arithmetic_logic" +version = "0.1.0" +edition = "2025_12" + + +[lib] +sierra = true + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[executable] + +[cairo] +enable-gas = false + +[dependencies] +cairo_execute = "2.17.0" + diff --git a/yahia008/av2_pro/arithmetic_logic/src/lib.cairo b/yahia008/av2_pro/arithmetic_logic/src/lib.cairo new file mode 100644 index 0000000..7fa7860 --- /dev/null +++ b/yahia008/av2_pro/arithmetic_logic/src/lib.cairo @@ -0,0 +1 @@ +pub mod mylogic; \ No newline at end of file diff --git a/yahia008/av2_pro/arithmetic_logic/src/mylogic.cairo b/yahia008/av2_pro/arithmetic_logic/src/mylogic.cairo new file mode 100644 index 0000000..b91c594 --- /dev/null +++ b/yahia008/av2_pro/arithmetic_logic/src/mylogic.cairo @@ -0,0 +1,46 @@ +use core::num::traits::CheckedAdd; +use core::num::traits::CheckedSub; +use core::num::traits::CheckedMul; + +#[derive(Drop)] +enum MathError { + Overflow, + DivisionByZero, + Underflow, +} + + +pub fn add_num(x: u128, y: u128) -> u128{ + if y == 0 + { + return x; + } + x.checked_add(y).expect('overflow') +} + +pub fn sub_num(x: u128, y: u128) -> u128 { + if y == 0 + { + return x; + } + x.checked_sub(y).expect('underflow') +} + +pub fn multiply_number(x:u128, y:u128) -> Result { + if x == 0 || y == 0 { + return Result::Ok(0); + } + x.checked_mul(y).ok_or(MathError::Overflow) +} + +pub fn divide(x: u128, y: u128) -> Result{ + if y == 0 { + return Result::Err(MathError::DivisionByZero); + } + + Result::Ok(x / y) +} + +pub fn reset_count() -> u128 { + 0 +} \ No newline at end of file diff --git a/yahia008/av2_pro/counterv2_pro/.gitignore b/yahia008/av2_pro/counterv2_pro/.gitignore new file mode 100644 index 0000000..4096f8b --- /dev/null +++ b/yahia008/av2_pro/counterv2_pro/.gitignore @@ -0,0 +1,5 @@ +target +.snfoundry_cache/ +snfoundry_trace/ +coverage/ +profile/ diff --git a/yahia008/av2_pro/counterv2_pro/Scarb.lock b/yahia008/av2_pro/counterv2_pro/Scarb.lock new file mode 100644 index 0000000..bcc4c05 --- /dev/null +++ b/yahia008/av2_pro/counterv2_pro/Scarb.lock @@ -0,0 +1,29 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "arithmetic_logic" +version = "0.1.0" + +[[package]] +name = "counterv2_pro" +version = "0.1.0" +dependencies = [ + "arithmetic_logic", + "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/yahia008/av2_pro/counterv2_pro/Scarb.toml b/yahia008/av2_pro/counterv2_pro/Scarb.toml new file mode 100644 index 0000000..059e06e --- /dev/null +++ b/yahia008/av2_pro/counterv2_pro/Scarb.toml @@ -0,0 +1,53 @@ +[package] +name = "counterv2_pro" +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.17.0" +arithmetic_logic = {path = "../arithmetic_logic"} + +[dev-dependencies] +snforge_std = "0.59.0" +assert_macros = "2.17.0" + +[[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/yahia008/av2_pro/counterv2_pro/snfoundry.toml b/yahia008/av2_pro/counterv2_pro/snfoundry.toml new file mode 100644 index 0000000..686c2ab --- /dev/null +++ b/yahia008/av2_pro/counterv2_pro/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://api.zan.top/public/starknet-sepolia/rpc/v0_10" # 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 = "Voyager" # 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/yahia008/av2_pro/counterv2_pro/src/counter.cairo b/yahia008/av2_pro/counterv2_pro/src/counter.cairo new file mode 100644 index 0000000..7d9c225 --- /dev/null +++ b/yahia008/av2_pro/counterv2_pro/src/counter.cairo @@ -0,0 +1,76 @@ +use arithmetic_logic::mylogic; +use starknet::{ContractAddress}; +#[starknet::interface] +pub trait ICounter { + fn get_count(self: @T) -> u128; + fn get_owner(self: @T) -> ContractAddress; + fn transfer_ownership(ref self: T, new_owner: ContractAddress); + fn increase_count(ref self: T, amount: u128); + fn decrease_count (ref self:T, amount:u128); + fn reset_count(ref self:T); +} + +#[starknet::contract] +pub mod CounterV2{ + +use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + + use super::ICounter; + use starknet::ContractAddress; + use starknet::get_caller_address; + use super::mylogic; + + #[storage] + struct Storage{ + count:u128, + owner:ContractAddress + } + + #[constructor] + fn constructor(ref self:ContractState, initial_owner:ContractAddress){ + self.owner.write(initial_owner); + self.count.write(0); + } + + #[abi(embed_v0)] + impl ICounterimpl of ICounter { + + fn get_count(self: @ContractState) -> u128 { + self.count.read() + } + fn get_owner(self: @ContractState) -> ContractAddress{ + self.owner.read() + } + fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress){ + let caller = get_caller_address(); + assert!(caller == self.owner.read(), "Only owner can transfer"); + self.owner.write(new_owner); + } + fn increase_count(ref self: ContractState, amount: u128){ + let caller:ContractAddress = get_caller_address(); + assert!(caller == self.owner.read(), "Only owner can change state"); + + let current = self.count.read(); + let new:u128 = mylogic::add_num(current, amount); + self.count.write(new); + } + fn decrease_count (ref self:ContractState, amount:u128){ + let caller:ContractAddress = get_caller_address(); + assert!(caller == self.owner.read(), "Only owner can change state"); + + let current = self.count.read(); + let new:u128 = mylogic::sub_num(current, amount); + self.count.write(new); + } + fn reset_count(ref self:ContractState){ + let caller = get_caller_address(); + assert!(caller == self.owner.read(), "Only owner can change state"); + let current= self.count.read(); + if current != 0 { + let new:u128 = mylogic::reset_count(); + self.count.write(new); + } + // emit it already zero + } + } +} \ No newline at end of file diff --git a/yahia008/av2_pro/counterv2_pro/src/lib.cairo b/yahia008/av2_pro/counterv2_pro/src/lib.cairo new file mode 100644 index 0000000..22bde39 --- /dev/null +++ b/yahia008/av2_pro/counterv2_pro/src/lib.cairo @@ -0,0 +1,4 @@ +mod counter; +pub use counter::CounterV2; +pub use counter::ICounterDispatcher; +pub use counter::ICounterDispatcherTrait; \ No newline at end of file diff --git a/yahia008/av2_pro/counterv2_pro/tests/test_couter.cairo b/yahia008/av2_pro/counterv2_pro/tests/test_couter.cairo new file mode 100644 index 0000000..5b5afcd --- /dev/null +++ b/yahia008/av2_pro/counterv2_pro/tests/test_couter.cairo @@ -0,0 +1,235 @@ +use starknet::SyscallResultTrait; +use snforge_std::DeclareResultTrait; +use counterv2_pro::CounterV2; +use starknet::testing::{ + set_contract_address, set_caller_address +}; +use snforge_std::{declare, ContractClassTrait, start_cheat_caller_address, }; +use counterv2_pro::{ICounterDispatcher, ICounterDispatcherTrait}; +use starknet::syscalls::deploy_syscall; +use starknet::ContractAddress; +use starknet::contract_address_const; + +const U128_MAX: u128 = 340282366920938463463374607431768211455; + + fn owner() -> ContractAddress { + + contract_address_const::<0x1>() + } + + fn attacker() -> ContractAddress { + contract_address_const::<0x2>() + } + + fn new_owner() -> ContractAddress { + contract_address_const::<0x3>() + } + +fn deploy_contract(initial_owner: ContractAddress) -> ICounterDispatcher { + let contract = declare("CounterV2").unwrap_syscall().contract_class(); + + let mut calldata = array![initial_owner.into()]; + + let (contract_address, _) = contract.deploy(@calldata).unwrap_syscall(); + + ICounterDispatcher { contract_address } +} + +#[cfg(test)] +mod tests { + use super::*; + + use super::new_owner; +use counterv2_pro::ICounterDispatcherTrait; +use snforge_std::start_cheat_caller_address; +use super::deploy_contract; +use super::attacker; +use super::owner; +use starknet::SyscallResultTrait; + #[test] + fn test_constructor(){ + let initial_owner = owner(); + let counter = deploy_contract(initial_owner); + assert!(counter.get_count() == 0, "initial count should be zero") + assert!(counter.get_owner() == initial_owner, "owner is initial owner"); + } + + #[test] + fn test_increase_count() { + let owner = owner(); + let counter = deploy_contract(owner); + + start_cheat_caller_address(counter.contract_address, owner); + + counter.increase_count(5); + assert_eq!(counter.get_count(), 5); + + counter.increase_count(3); + assert_eq!(counter.get_count(), 8); + } + + #[test] + #[should_panic(expected: "Only owner can change state")] + fn test_increase_count_not_owner() { + let owner = owner(); + let counter = deploy_contract(owner); + + let attacker = attacker(); + start_cheat_caller_address(counter.contract_address, attacker); + + counter.increase_count(5); // Should panic + } + #[test] + fn test_increase_count_by_zero() { + let owner = owner(); + let counter = deploy_contract(owner); + + start_cheat_caller_address(counter.contract_address, owner); + counter.increase_count(0); + assert_eq!(counter.get_count(), 0); + + counter.increase_count(5); + counter.increase_count(0); + + assert_eq!(counter.get_count(), 5,); + + } + + #[test] + #[should_panic] + + fn test_increase_count_overflow() { + let owner = owner(); + let counter = deploy_contract(owner); + + start_cheat_caller_address(counter.contract_address, owner); + + counter.increase_count(U128_MAX); + assert_eq!(counter.get_count(), U128_MAX); + + counter.increase_count(1); +} + + #[test] + fn test_decrease_count_by_owner() { + let owner = owner(); + let counter = deploy_contract(owner); + + start_cheat_caller_address(counter.contract_address, owner); + counter.increase_count(20); + counter.decrease_count(7); + assert!(counter.get_count() == 13, "Count should be 13 after decrease"); + } + + + #[test] + fn test_decrease_count_to_zero() { + let owner = owner(); + let counter = deploy_contract(owner); + start_cheat_caller_address(counter.contract_address, owner); + counter.increase_count(5); + counter.decrease_count(5); + assert!(counter.get_count() == 0, "Count should reach exactly 0"); + } + + #[test] + #[should_panic] + fn test_decrease_count_below_zero_panics() { + let owner = owner(); + let counter = deploy_contract(owner); + + start_cheat_caller_address(counter.contract_address, owner); + + counter.decrease_count(1); // count is 0, underflow expected + } + + + #[test] + #[should_panic(expected: "Only owner can change state")] + fn test_decrease_count_by_non_owner_panics() { + let owner = owner(); + let counter = deploy_contract(owner); + counter.increase_count(10); + + start_cheat_caller_address(counter.contract_address, attacker()); + counter.decrease_count(5); + } + + #[test] + fn test_decrease_count_by_zero() { + let owner = owner(); + let counter = deploy_contract(owner); + + start_cheat_caller_address(counter.contract_address, owner); + counter.increase_count(10); + assert_eq!(counter.get_count(), 10); + + counter.decrease_count(0); + + assert_eq!(counter.get_count(), 10,); + + } + + #[test] + fn test_reset_count() { + let owner = contract_address_const::<'owner'>(); + let counter = deploy_contract(owner); + + start_cheat_caller_address(counter.contract_address, owner); + + counter.reset_count(); + assert_eq!(counter.get_count(), 0); + + counter.increase_count(42); + assert_eq!(counter.get_count(), 42); + + counter.reset_count(); + assert_eq!(counter.get_count(), 0); + + counter.reset_count(); + assert_eq!(counter.get_count(), 0); + } + + + #[test] + #[should_panic(expected: "Only owner can change state")] + fn test_reset_count_not_owner() { + let owner = contract_address_const::<'owner'>(); + let counter = deploy_contract(owner); + + let attacker = attacker(); + start_cheat_caller_address(counter.contract_address, attacker); + + counter.reset_count(); + } + + #[test] + fn test_transfer_ownership() { + let owner = owner(); + let new_owner = new_owner(); + let counter = deploy_contract(owner); + + start_cheat_caller_address(counter.contract_address, owner); + + counter.transfer_ownership(new_owner); + assert_eq!(counter.get_owner(), new_owner); + + // New owner should now have access + start_cheat_caller_address(counter.contract_address, new_owner); + counter.increase_count(5); + assert_eq!(counter.get_count(), 5); + } + + #[test] + #[should_panic(expected: "Only owner can transfer")] + fn test_transfer_ownership_not_owner() { + let owner = owner(); + let counter = deploy_contract(owner); + + let attacker = attacker(); + start_cheat_caller_address(counter.contract_address, attacker); + + counter.transfer_ownership(attacker); // Should panic + } + + } diff --git a/yahia008/av2_pro/use_arithmetic/.gitignore b/yahia008/av2_pro/use_arithmetic/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/yahia008/av2_pro/use_arithmetic/.gitignore @@ -0,0 +1 @@ +target diff --git a/yahia008/av2_pro/use_arithmetic/Scarb.lock b/yahia008/av2_pro/use_arithmetic/Scarb.lock new file mode 100644 index 0000000..c8d9a3c --- /dev/null +++ b/yahia008/av2_pro/use_arithmetic/Scarb.lock @@ -0,0 +1,13 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "arithmetic_logic" +version = "0.1.0" + +[[package]] +name = "use_arithmetic" +version = "0.1.0" +dependencies = [ + "arithmetic_logic", +] diff --git a/yahia008/av2_pro/use_arithmetic/Scarb.toml b/yahia008/av2_pro/use_arithmetic/Scarb.toml new file mode 100644 index 0000000..3fdc2ec --- /dev/null +++ b/yahia008/av2_pro/use_arithmetic/Scarb.toml @@ -0,0 +1,15 @@ +[package] +name = "use_arithmetic" +version = "0.1.0" +edition = "2025_12" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[executable] + +[cairo] +enable-gas = false + +[dependencies] +cairo_execute = "2.17.0" +arithmetic_logic = {path = "../arithmetic_logic"} diff --git a/yahia008/av2_pro/use_arithmetic/src/hello_world.cairo b/yahia008/av2_pro/use_arithmetic/src/hello_world.cairo new file mode 100644 index 0000000..437ca78 --- /dev/null +++ b/yahia008/av2_pro/use_arithmetic/src/hello_world.cairo @@ -0,0 +1,31 @@ +use arithmetic_logic::mylogic; +#[executable] + +fn main() { + + let add_result = mylogic::add_num(10, 5); + assert!(add_result == 15, "result must be 15 "); + + let sub_result = mylogic::sub_num(10, 5); + assert!(sub_result == 5, "result mudt be 5"); + + let mul_result = mylogic::multiply_number(10, 5); + match mul_result { + Result::Ok(v) => { + assert!(v==50, "assertion failed") + println!("mul value {}", v) + }, + Result::Err(_) => println!("mul error"), + }; + + + let div_result = mylogic::divide(10, 2); + match div_result { + Result::Ok(v) => { + assert!(v==5, "assertion failed") + println!("div value {}", v) + }, + Result::Err(_) => println!("div error", ), + }; + +} diff --git a/yahia008/av2_pro/use_arithmetic/src/lib.cairo b/yahia008/av2_pro/use_arithmetic/src/lib.cairo new file mode 100644 index 0000000..e5eb338 --- /dev/null +++ b/yahia008/av2_pro/use_arithmetic/src/lib.cairo @@ -0,0 +1 @@ +mod hello_world; diff --git a/yahia008/blockheader_test/.gitignore b/yahia008/blockheader_test/.gitignore new file mode 100644 index 0000000..4096f8b --- /dev/null +++ b/yahia008/blockheader_test/.gitignore @@ -0,0 +1,5 @@ +target +.snfoundry_cache/ +snfoundry_trace/ +coverage/ +profile/ diff --git a/yahia008/blockheader_test/Scarb.lock b/yahia008/blockheader_test/Scarb.lock new file mode 100644 index 0000000..d13958f --- /dev/null +++ b/yahia008/blockheader_test/Scarb.lock @@ -0,0 +1,24 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "blockheader_test" +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/yahia008/blockheader_test/Scarb.toml b/yahia008/blockheader_test/Scarb.toml new file mode 100644 index 0000000..2407a1f --- /dev/null +++ b/yahia008/blockheader_test/Scarb.toml @@ -0,0 +1,52 @@ +[package] +name = "blockheader_test" +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.17.0" + +[dev-dependencies] +snforge_std = "0.59.0" +assert_macros = "2.17.0" + +[[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/yahia008/blockheader_test/snfoundry.toml b/yahia008/blockheader_test/snfoundry.toml new file mode 100644 index 0000000..686c2ab --- /dev/null +++ b/yahia008/blockheader_test/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://api.zan.top/public/starknet-sepolia/rpc/v0_10" # 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 = "Voyager" # 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/yahia008/blockheader_test/src/lib.cairo b/yahia008/blockheader_test/src/lib.cairo new file mode 100644 index 0000000..2108f68 --- /dev/null +++ b/yahia008/blockheader_test/src/lib.cairo @@ -0,0 +1 @@ +pub mod token; \ No newline at end of file diff --git a/yahia008/blockheader_test/src/token.cairo b/yahia008/blockheader_test/src/token.cairo new file mode 100644 index 0000000..e70aca7 --- /dev/null +++ b/yahia008/blockheader_test/src/token.cairo @@ -0,0 +1,358 @@ +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IERC20 { + fn get_name(self: @TContractState) -> felt252; + fn get_symbol(self: @TContractState) -> felt252; + fn get_decimals(self: @TContractState) -> u8; + fn get_total_supply(self: @TContractState) -> u256; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256); + fn transfer_from( + ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256, + ); + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256); + fn increase_allowance(ref self: TContractState, spender: ContractAddress, added_value: u256); + fn decrease_allowance( + ref self: TContractState, spender: ContractAddress, subtracted_value: u256, + ); +} + +#[starknet::interface] +pub trait IAdminRestricted { + fn set_transfer_limit(ref self: TContractState, new_limit: u256); + fn burn(ref self: TContractState, account: ContractAddress, amount: u256); + fn revoke_transfers(ref self: TContractState); + fn enable_transfers(ref self: TContractState); + fn get_transfer_limit(self: @TContractState) -> u256; + fn is_transfers_enabled(self: @TContractState) -> bool; + fn admin_revoke_spender( + ref self: TContractState, owner: ContractAddress, spender: ContractAddress, + ); +} + +#[starknet::contract] +pub mod erc20 { + use core::num::traits::{Bounded, Zero}; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess, + }; + use starknet::{ContractAddress, contract_address_const, get_caller_address}; + + #[storage] + struct Storage { + name: felt252, + symbol: felt252, + decimals: u8, + total_supply: u256, + balances: Map, + allowances: Map<(ContractAddress, ContractAddress), u256>, + revoked_user: Map, + admin: ContractAddress, + transfer_limit: u256, + transfers_enabled: bool, + } + + #[event] + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub enum Event { + Transfer: Transfer, + Approval: Approval, + TransferLimitUpdated: TransferLimitUpdated, + TransfersRevoked: TransfersRevoked, + TransfersEnabled: TransfersEnabled, + Burn: Burn, + } + + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct Transfer { + #[key] + pub from: ContractAddress, + #[key] + pub to: ContractAddress, + pub value: u256, + } + + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct Approval { + #[key] + pub owner: ContractAddress, + #[key] + pub spender: ContractAddress, + pub value: u256, + } + + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct TransferLimitUpdated { + pub old_limit: u256, + pub new_limit: u256, + pub updated_by: ContractAddress, + } + + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct TransfersRevoked { + pub revoked_by: ContractAddress, + } + + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct TransfersEnabled { + pub enabled_by: ContractAddress, + } + + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct Burn { + #[key] + pub from: ContractAddress, + pub value: u256, + pub burned_by: ContractAddress, + } + + mod Errors { + pub const APPROVE_TO_ZERO: felt252 = 'ERC20: approve to 0'; + pub const TRANSFER_FROM_ZERO: felt252 = 'ERC20: transfer from 0'; + pub const TRANSFER_TO_ZERO: felt252 = 'ERC20: transfer to 0'; + pub const BURN_FROM_ZERO: felt252 = 'ERC20: burn from 0'; + pub const MINT_TO_ZERO: felt252 = 'ERC20: mint to 0'; + pub const NOT_ADMIN: felt252 = 'ERC20: not admin'; + pub const TRANSFERS_DISABLED: felt252 = 'ERC20: transfers disabled'; + pub const EXCEEDS_MAX_LIMIT: felt252 = 'ERC20: exceeds max limit'; + pub const INSUFFICIENT_BALANCE: felt252 = 'ERC20: insufficient balance'; + pub const INVALID_LIMIT: felt252 = 'ERC20: limit must be >0'; + pub const BURN_AMOUNT_ZERO: felt252 = 'ERC20: burn amount >0'; + pub const INSUFFICIENT_ALLOWANCE: felt252 = 'ERC20: insufficient allowance'; + pub const SPENDER_REVOKED: felt252 = 'ERC20: spender revoked'; + pub const ZERO_ADDRESS: felt252 = 'ERC20: zero address'; + } + + // Adjusted limit assuming 18 decimals for context, tweak as needed! + const MAX_LIMIT: u256 = 10000_u256; + + #[constructor] + fn constructor( + ref self: ContractState, + recipient: ContractAddress, + name: felt252, + decimals: u8, + initial_supply: u256, + symbol: felt252, + admin: ContractAddress, + ) { + assert(admin.is_non_zero(), Errors::ZERO_ADDRESS); + self.name.write(name); + self.symbol.write(symbol); + self.decimals.write(decimals); + self.admin.write(admin); + self.transfer_limit.write(MAX_LIMIT); + self.transfers_enabled.write(true); + self.mint(recipient, initial_supply); + } + + #[abi(embed_v0)] + impl IERC20Impl of super::IERC20 { + fn get_name(self: @ContractState) -> felt252 { + self.name.read() + } + fn get_symbol(self: @ContractState) -> felt252 { + self.symbol.read() + } + fn get_decimals(self: @ContractState) -> u8 { + self.decimals.read() + } + fn get_total_supply(self: @ContractState) -> u256 { + self.total_supply.read() + } + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + self.balances.read(account) + } + fn allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress, + ) -> u256 { + self.allowances.read((owner, spender)) + } + + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) { + let sender = get_caller_address(); + self._validate_transfer(sender, recipient, amount); + self._transfer(sender, recipient, amount); + } + + fn transfer_from( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256, + ) { + let caller = get_caller_address(); + + assert(!self.revoked_user.read(caller), Errors::SPENDER_REVOKED); + + self._validate_transfer(sender, recipient, amount); + self.spend_allowance(sender, caller, amount); + self._transfer(sender, recipient, amount); + } + + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) { + let caller = get_caller_address(); + assert(!self.revoked_user.read(spender), Errors::SPENDER_REVOKED); + assert(amount != 0, 'amount cant be zero'); + self.approve_helper(caller, spender, amount); + } + + fn increase_allowance( + ref self: ContractState, spender: ContractAddress, added_value: u256, + ) { + self._only_admin(); + let caller = get_caller_address(); + assert(!self.revoked_user.read(spender), Errors::SPENDER_REVOKED); + let current_allowance = self.allowances.read((caller, spender)); + self.approve_helper(caller, spender, current_allowance + added_value); + } + + fn decrease_allowance( + ref self: ContractState, spender: ContractAddress, subtracted_value: u256, + ) { + self._only_admin(); + let caller = get_caller_address(); + let current_allowance = self.allowances.read((caller, spender)); + assert(current_allowance >= subtracted_value, Errors::INSUFFICIENT_ALLOWANCE); + self.approve_helper(caller, spender, current_allowance - subtracted_value); + } + } + + #[abi(embed_v0)] + impl IAdminRestrictedImpl of super::IAdminRestricted { + fn set_transfer_limit(ref self: ContractState, new_limit: u256) { + self._only_admin(); + assert(new_limit > 0, Errors::INVALID_LIMIT); + //assert(new_limit <= MAX_LIMIT, Errors::EXCEEDS_MAX_LIMIT); + + let old_limit = self.transfer_limit.read(); + self.transfer_limit.write(new_limit); + + self + .emit( + TransferLimitUpdated { old_limit, new_limit, updated_by: get_caller_address() }, + ); + } + + fn burn(ref self: ContractState, account: ContractAddress, amount: u256) { + self._only_admin(); + assert(account.is_non_zero(), Errors::BURN_FROM_ZERO); + assert(amount > 0, Errors::BURN_AMOUNT_ZERO); + + let balance = self.balances.read(account); + assert(balance >= amount, Errors::INSUFFICIENT_BALANCE); + + self.balances.write(account, balance - amount); + self.total_supply.write(self.total_supply.read() - amount); + + let zero_address = Zero::zero(); + let zero_balance = self.balances.read(zero_address); + self.balances.write(zero_address, zero_balance + amount); + + self.emit(Burn { from: account, value: amount, burned_by: get_caller_address() }); + self.emit(Transfer { from: account, to: zero_address, value: amount }); + } + + fn revoke_transfers(ref self: ContractState) { + self._only_admin(); + assert(self.transfers_enabled.read() == true, 'transfer not enabled'); + self.transfers_enabled.write(false); + self.emit(TransfersRevoked { revoked_by: get_caller_address() }); + } + + fn enable_transfers(ref self: ContractState) { + self._only_admin(); + assert(self.transfers_enabled.read() == false, 'transfer enabled'); + self.transfers_enabled.write(true); + self.emit(TransfersEnabled { enabled_by: get_caller_address() }); + } + + fn get_transfer_limit(self: @ContractState) -> u256 { + self.transfer_limit.read() + } + fn is_transfers_enabled(self: @ContractState) -> bool { + self.transfers_enabled.read() + } + + fn admin_revoke_spender( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, + ) { + self._only_admin(); + assert(owner.is_non_zero() && spender.is_non_zero(), Errors::ZERO_ADDRESS); + + let is_already_revoked = self.revoked_user.read(spender); + assert(!is_already_revoked, 'ERC20: already revoked'); + + self.approve_helper(owner, spender, 0); + self.revoked_user.write(spender, true); + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn _only_admin(self: @ContractState) { + let caller = get_caller_address(); + assert(caller == self.admin.read(), Errors::NOT_ADMIN); + } + + fn _validate_transfer( + self: @ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256, + ) { + assert(sender.is_non_zero(), Errors::TRANSFER_FROM_ZERO); + assert(recipient.is_non_zero(), Errors::TRANSFER_TO_ZERO); + assert(self.transfers_enabled.read(), Errors::TRANSFERS_DISABLED); + + let current_limit = self.transfer_limit.read(); + assert(amount <= current_limit, Errors::EXCEEDS_MAX_LIMIT); + + let sender_balance = self.balances.read(sender); + assert(sender_balance >= amount, Errors::INSUFFICIENT_BALANCE); + } + + fn _transfer( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256, + ) { + // assert(self.transfers_enabled.read() == true, 'transfer is not enable'); + assert(sender.is_non_zero(), Errors::ZERO_ADDRESS); + assert(recipient.is_non_zero(), Errors::ZERO_ADDRESS); + let sender_balance = self.balances.read(sender); + self.balances.write(sender, sender_balance - amount); + self.balances.write(recipient, self.balances.read(recipient) + amount); + self.emit(Transfer { from: sender, to: recipient, value: amount }); + } + + fn spend_allowance( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256, + ) { + let current_allowance = self.allowances.read((owner, spender)); + if current_allowance != Bounded::MAX { + assert(current_allowance >= amount, Errors::INSUFFICIENT_ALLOWANCE); + self.allowances.write((owner, spender), current_allowance - amount); + } + } + + fn approve_helper( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256, + ) { + assert(spender.is_non_zero(), Errors::APPROVE_TO_ZERO); + self.allowances.write((owner, spender), amount); + self.emit(Approval { owner, spender, value: amount }); + } + + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + assert(recipient.is_non_zero(), Errors::MINT_TO_ZERO); + self.total_supply.write(self.total_supply.read() + amount); + self.balances.write(recipient, self.balances.read(recipient) + amount); + self + .emit( + Transfer { from: contract_address_const::<0>(), to: recipient, value: amount }, + ); + } + } +}