diff --git a/src/erc20.cairo b/src/erc20.cairo new file mode 100644 index 0000000..1681537 --- /dev/null +++ b/src/erc20.cairo @@ -0,0 +1,120 @@ +#[contract] +mod ERC20 { + use zeroable::Zeroable; + use starknet::get_caller_address; + use starknet::contract_address_const; + use starknet::ContractAddressZeroable; + + struct Storage { + name: felt, + symbol: felt, + decimals: u8, + total_supply: u256, + balances: LegacyMap::, + allowances: LegacyMap::<(ContractAddress, ContractAddress), u256>, + } + + #[event] + fn Transfer(from: ContractAddress, to: ContractAddress, value: u256) {} + + #[event] + fn Approval(owner: ContractAddress, spender: ContractAddress, value: u256) {} + + #[constructor] + fn constructor( + name_: felt, symbol_: felt, decimals_: u8, initial_supply: u256, recipient: ContractAddress + ) { + name::write(name_); + symbol::write(symbol_); + decimals::write(decimals_); + assert(!recipient.is_zero(), 'ERC20: mint to the 0 address'); + total_supply::write(initial_supply); + balances::write(recipient, initial_supply); + Transfer(contract_address_const::<0>(), recipient, initial_supply); + } + + #[view] + fn get_name() -> felt { + name::read() + } + + #[view] + fn get_symbol() -> felt { + symbol::read() + } + + #[view] + fn get_decimals() -> u8 { + decimals::read() + } + + #[view] + fn get_total_supply() -> u256 { + total_supply::read() + } + + #[view] + fn balance_of(account: ContractAddress) -> u256 { + balances::read(account) + } + + #[view] + fn allowance(owner: ContractAddress, spender: ContractAddress) -> u256 { + allowances::read((owner, spender)) + } + + #[external] + fn transfer(recipient: ContractAddress, amount: u256) { + let sender = get_caller_address(); + transfer_helper(sender, recipient, amount); + } + + #[external] + fn transfer_from(sender: ContractAddress, recipient: ContractAddress, amount: u256) { + let caller = get_caller_address(); + spend_allowance(sender, caller, amount); + transfer_helper(sender, recipient, amount); + } + + #[external] + fn approve(spender: ContractAddress, amount: u256) { + let caller = get_caller_address(); + approve_helper(caller, spender, amount); + } + + #[external] + fn increase_allowance(spender: ContractAddress, added_value: u256) { + let caller = get_caller_address(); + approve_helper(caller, spender, allowances::read((caller, spender)) + added_value); + } + + #[external] + fn decrease_allowance(spender: ContractAddress, subtracted_value: u256) { + let caller = get_caller_address(); + approve_helper(caller, spender, allowances::read((caller, spender)) - subtracted_value); + } + + fn transfer_helper(sender: ContractAddress, recipient: ContractAddress, amount: u256) { + assert(!sender.is_zero(), 'ERC20: transfer from 0'); + assert(!recipient.is_zero(), 'ERC20: transfer to 0'); + balances::write(sender, balances::read(sender) - amount); + balances::write(recipient, balances::read(recipient) + amount); + Transfer(sender, recipient, amount); + } + + fn spend_allowance(owner: ContractAddress, spender: ContractAddress, amount: u256) { + let current_allowance = allowances::read((owner, spender)); + let ONES_MASK = 0xffffffffffffffffffffffffffffffff_u128; + let is_unlimited_allowance = + current_allowance.low == ONES_MASK & current_allowance.high == ONES_MASK; + if !is_unlimited_allowance { + approve_helper(owner, spender, current_allowance - amount); + } + } + + fn approve_helper(owner: ContractAddress, spender: ContractAddress, amount: u256) { + assert(!spender.is_zero(), 'ERC20: approve from 0'); + allowances::write((owner, spender), amount); + Approval(owner, spender, amount); + } +} diff --git a/src/lib.cairo b/src/lib.cairo index de44212..a035254 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1,4 +1,5 @@ // All modules must be present here +mod erc20; mod math; mod test; \ No newline at end of file