diff --git a/src/bwc_erc20_token.cairo b/src/bwc_erc20_token.cairo index 17ef726..136c619 100644 --- a/src/bwc_erc20_token.cairo +++ b/src/bwc_erc20_token.cairo @@ -33,24 +33,22 @@ mod BWCERC20Token { name: felt252, symbol: felt252, decimals: u8, - total_supply: u256, - balances: LegacyMap, - allowances: LegacyMap< - (ContractAddress, ContractAddress), u256 - >, //similar to mapping(address => mapping(address => uint256)) + totalSupply: u256, + allowances: LegacyMap::<(ContractAddress, ContractAddress), u256>, + balances: LegacyMap:: } - // Event + #[event] #[derive(Drop, starknet::Event)] enum Event { - Approval: Approval, - Transfer: Transfer + transfer: Transfer, + approval: Approval } #[derive(Drop, starknet::Event)] struct Transfer { - from: ContractAddress, - to: ContractAddress, + sender: ContractAddress, + reciever: ContractAddress, value: u256 } @@ -58,163 +56,86 @@ mod BWCERC20Token { struct Approval { owner: ContractAddress, spender: ContractAddress, - value: u256, + value: u256 } - // Note: The contract constructor is not part of the interface. Nor are internal functions part of the interface. - - // Constructor + // contruct value to insert important perequsite values #[constructor] fn constructor( ref self: ContractState, + _owner: ContractAddress, _name: felt252, _symbol: felt252, - _decimal: u8, - _initial_supply: u256, - recipient: ContractAddress + _decimals: u8 ) { - // The .is_zero() method here is used to determine whether the address type recipient is a 0 address, similar to recipient == address(0) in Solidity. - assert(!recipient.is_zero(), 'transfer to zero address'); self.name.write(_name); self.symbol.write(_symbol); - self.decimals.write(_decimal); - self.total_supply.write(_initial_supply); - self.balances.write(recipient, _initial_supply); - - self - .emit( - Transfer { - //Here, `contract_address_const::<0>()` is similar to address(0) in Solidity - from: contract_address_const::<0>(), to: recipient, value: _initial_supply - } - ); + self.decimals.write(_decimals); } + // errors + mod Errors { + const ZERO_ADDRESS_ERROR: felt252 = 'This address is not allowed'; + const ZERO_VALUE: felt252 = 'this value is below minimum'; + const INSUFFICIENT_BALANCE: felt252 = 'insufficient balance'; + const ONLY_OWNER_ERROR: felt252 = 'caller not owner'; + } + + // fn to get total supply #[external(v0)] - impl IERC20Impl of 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 caller = get_caller_address(); - self.transfer_helper(caller, recipient, amount); - } - - fn transfer_from( - ref self: ContractState, - sender: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) { - let caller = get_caller_address(); - let my_allowance = self.allowances.read((sender, recipient)); - assert(my_allowance <= amount, 'Amount Not Allowed'); - self - .spend_allowance( - sender, caller, amount - ); //responsible for deduction of the amount allowed to spend - self.transfer_helper(sender, recipient, amount); - } - - fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) { - let caller = get_caller_address(); - self.approve_helper(caller, spender, amount); - } - - fn increase_allowance( - ref self: ContractState, spender: ContractAddress, added_value: u256 - ) { - let caller = get_caller_address(); - self - .approve_helper( - caller, spender, self.allowances.read((caller, spender)) + added_value - ); - } - - fn decrease_allowance( - ref self: ContractState, spender: ContractAddress, subtracted_value: u256 - ) { - let caller = get_caller_address(); - self - .approve_helper( - caller, spender, self.allowances.read((caller, spender)) - subtracted_value - ); - } + fn get_total_supply(self: @ContractState) -> u256 { + self.totalSupply.read() } - #[generate_trait] - impl HelperImpl of HelperTrait { - fn transfer_helper( - ref self: ContractState, - sender: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) { - let sender_balance = self.balance_of(sender); - - assert(!sender.is_zero(), 'transfer from 0'); - assert(!recipient.is_zero(), 'transfer to 0'); - assert(sender_balance >= amount, 'Insufficient fund'); - self.balances.write(sender, self.balances.read(sender) - amount); - self.balances.write(recipient, self.balances.read(recipient) + amount); - true; - - self.emit(Transfer { from: sender, to: recipient, value: amount, }); - } - - fn approve_helper( - ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 - ) { - assert(!owner.is_zero(), 'approve from 0'); - assert(!spender.is_zero(), 'approve to 0'); - - self.allowances.write((owner, spender), amount); - - self.emit(Approval { owner, spender, value: amount, }) - } - - fn spend_allowance( - ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 - ) { - // First, read the amount authorized by owner to spender - let current_allowance = self.allowances.read((owner, spender)); - - // define a variable ONES_MASK of type u128 - let ONES_MASK = 0xfffffffffffffffffffffffffffffff_u128; - - // to determine whether the authorization is unlimited, - - let is_unlimited_allowance = current_allowance.low == ONES_MASK - && current_allowance - .high == ONES_MASK; //equivalent to type(uint256).max in Solidity. - - // This is also a way to save gas, because if the authorized amount is the maximum value of u256, theoretically, this amount cannot be spent. - if !is_unlimited_allowance { - self.approve_helper(owner, spender, current_allowance - amount); - } - } + // fn to get balance of any user using address as key + #[external(v0)] + fn balanceOf(self: @ContractState, user_bal: ContractAddress) -> u256 { + self.balances.read(user_bal) + } + + // fn to transfer funds + #[external(v0)] + fn transfer(ref self: ContractState, _to: ContractAddress, _amount: u256) { + let caller = get_caller_address(); + let caller_balance = self.balances.read(caller); + let recievers_balance = self.balances.read(_to); + + // asserting amount is greatthan zero + assert(_amount > 0, Errors::ZERO_VALUE); + // asserting if caller_balance greater than amount to be sent + assert(caller_balance > _amount, Errors::INSUFFICIENT_BALANCE); + // asserting caller is not address zero + assert(!caller.is_zero(), Errors::ZERO_ADDRESS_ERROR); + + // deduct amount from caller + self.balances.write(caller, caller_balance - _amount); + // adding amount to reciever + self.balances.write(_to, recievers_balance + _amount); + + // emmit success event + self.emit(Transfer { sender: caller, reciever: _to, value: _amount }); + } + + // fn transfer from + #[external(v0)] + fn transferFrom( + ref self: ContractState, to: ContractAddress, from: ContractAddress, amount: u256 + ) { + let caller = get_caller_address(); + let sender_balance = self.balances.read(from); + let recievers_balance = self.balances.read(to); + + // asserting amount is greatthan zero + assert(amount > 0, Errors::ZERO_VALUE); + // asserting if caller_balance greater than amount to be sent + assert(sender_balance > amount, Errors::INSUFFICIENT_BALANCE); + // asserting caller is not address zero + assert(!caller.is_zero(), Errors::ZERO_ADDRESS_ERROR); + + self.balances.write(from, sender_balance - amount); + self.balances.write(to, recievers_balance + amount); + + // emmit success event + self.emit(Transfer { sender: from, reciever: to, value: amount }); } } diff --git a/src/class_character_v3.cairo b/src/class_character_v3.cairo new file mode 100644 index 0000000..18ca2b7 --- /dev/null +++ b/src/class_character_v3.cairo @@ -0,0 +1,188 @@ +// ============================================= +// ========= TRIATS ============================ +// trait definition +use starknet::{ContractAddress}; +#[starknet::interface] +trait IClassCharacter { + fn set_owner(ref self: T, new_owner: ContractAddress) -> bool; + fn get_owner(self: @T) -> ContractAddress; + fn set_student( + ref self: T, + _studentAddr: ContractAddress, + _name: felt252, + _age: u8, + _is_active: bool, + _has_reward: bool, + _xp_earnings: u256 + ) -> bool; + fn get_student(self: @T, studentAddr: ContractAddress) -> Student; +} + +// triat with access control +#[starknet::interface] +trait IClassCharacterWithAccessControl { + fn set_owner_with_access_control(ref self: T, new_owner: ContractAddress) -> bool; + fn get_owner_with_access_control(self: @T) -> ContractAddress; + fn set_student_with_access_control( + ref self: T, + _studentAddr: ContractAddress, + _name: felt252, + _age: u8, + _is_active: bool, + _has_reward: bool, + _xp_earnings: u256 + ) -> bool; + fn get_student_with_access_control(self: @T, student_addr: ContractAddress) -> Student; +} + +// ============================================== +// ======= Global vars ========================== +// student struct +#[derive(Copy, Drop, Serde, starknet::Store)] +struct Student { + name: felt252, + age: u8, + is_active: bool, + has_reward: bool, + xp_earnings: u256 +} + + +// ================================================ +// ========== SMART CONTRACT ====================== + +// classCharacterV3 smart contract +#[starknet::contract] +mod ClassCharacterV3 { + // libraries and trait imports + use core::zeroable::Zeroable; + use starknet::{ContractAddress, get_caller_address}; + use super::{IClassCharacter, IClassCharacterWithAccessControl, Student}; + + // ================================================ + // ================ CONSTRUCTOR =================== + #[constructor] + fn constructor(ref self: ContractState, _init_owner: ContractAddress) { + self.owner.write(_init_owner); + } + + // ================================================= + // ============ ERRORS ============================= + mod Errors { + const ZERO_ADDRESS_ERROR: felt252 = 'cant be called by address zero'; + const ONLY_OWNER_ERROR: felt252 = 'Only owner allowed'; + const ONLY_ACCOUNT_OWNER: felt252 = 'Only ACCT owner allowed'; + } + + // storage + #[storage] + struct Storage { + owner: ContractAddress, + students: LegacyMap:: + } + + //=========================================== + // ======= IMPLEMENTATIONS ================== + // triat without access control implementation + #[external(v0)] + impl classCharacterv3 of IClassCharacter { + fn get_owner(self: @ContractState) -> ContractAddress { + self.owner.read() + } + + fn set_owner(ref self: ContractState, new_owner: ContractAddress) -> bool { + self.owner.write(new_owner); + true + } + + fn get_student(self: @ContractState, studentAddr: ContractAddress) -> Student { + self.students.read(studentAddr) + } + + fn set_student( + ref self: ContractState, + _studentAddr: ContractAddress, + _name: felt252, + _age: u8, + _is_active: bool, + _has_reward: bool, + _xp_earnings: u256 + ) -> bool { + let student_instance = Student { + name: _name, + age: _age, + is_active: _is_active, + has_reward: _has_reward, + xp_earnings: _xp_earnings + }; + + self.students.write(_studentAddr, student_instance); + true + } + } + + // triat with access control implementation + #[external(v0)] + impl classCharacterv3WithAccessControl of IClassCharacterWithAccessControl { + fn get_owner_with_access_control(self: @ContractState) -> ContractAddress { + self.zero_address(); + self.owner.read() + } + + fn set_owner_with_access_control( + ref self: ContractState, new_owner: ContractAddress + ) -> bool { + self.zero_address(); + self.only_owner(); + self.owner.write(new_owner); + true + } + + fn get_student_with_access_control( + self: @ContractState, student_addr: ContractAddress + ) -> Student { + self.caller_is_student(student_addr); + self.students.read(student_addr) + } + + fn set_student_with_access_control( + ref self: ContractState, + _studentAddr: ContractAddress, + _name: felt252, + _age: u8, + _is_active: bool, + _has_reward: bool, + _xp_earnings: u256 + ) -> bool { + let student_instance = Student { + name: _name, + age: _age, + is_active: _is_active, + has_reward: _has_reward, + xp_earnings: _xp_earnings + }; + self.only_owner(); + self.students.write(_studentAddr, student_instance); + true + } + } + + #[generate_trait] + impl Private of PrivateTrait { + fn only_owner(self: @ContractState) { + let caller = get_caller_address(); + let owner = self.owner.read(); + assert(caller == owner, Errors::ONLY_OWNER_ERROR) + } + + fn zero_address(self: @ContractState) { + let caller = get_caller_address(); + assert(!caller.is_zero(), Errors::ZERO_ADDRESS_ERROR) + } + + fn caller_is_student(self: @ContractState, studentAddr: ContractAddress) { + let caller = get_caller_address(); + assert(caller == studentAddr, Errors::ONLY_ACCOUNT_OWNER) + } + } +} diff --git a/src/lib.cairo b/src/lib.cairo index 243f3e3..4ca1ebd 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1,5 +1,6 @@ mod class_character; mod class_character_v2; +mod class_character_v3; mod owner_contract; mod bwc_erc20_token; mod counter;