diff --git a/listings/applications/erc20/src/lib.cairo b/listings/applications/erc20/src/lib.cairo index 4ab2be17..40d3ff58 100644 --- a/listings/applications/erc20/src/lib.cairo +++ b/listings/applications/erc20/src/lib.cairo @@ -1,4 +1 @@ mod token; - -#[cfg(test)] -mod tests; diff --git a/listings/applications/erc20/src/tests.cairo b/listings/applications/erc20/src/tests.cairo deleted file mode 100644 index 361dba07..00000000 --- a/listings/applications/erc20/src/tests.cairo +++ /dev/null @@ -1,2 +0,0 @@ -mod tests { // TODO -} diff --git a/listings/applications/erc20/src/token.cairo b/listings/applications/erc20/src/token.cairo index da9f19a8..51660642 100644 --- a/listings/applications/erc20/src/token.cairo +++ b/listings/applications/erc20/src/token.cairo @@ -2,7 +2,7 @@ use starknet::ContractAddress; // ANCHOR: interface #[starknet::interface] -trait IERC20 { +pub trait IERC20 { fn get_name(self: @TContractState) -> felt252; fn get_symbol(self: @TContractState) -> felt252; fn get_decimals(self: @TContractState) -> u8; @@ -28,7 +28,7 @@ trait IERC20 { // ANCHOR: erc20 #[starknet::contract] -mod erc20 { +pub mod erc20 { use core::num::traits::Zero; use starknet::get_caller_address; use starknet::contract_address_const; @@ -45,22 +45,22 @@ mod erc20 { } #[event] - #[derive(Drop, starknet::Event)] - enum Event { + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub enum Event { Transfer: Transfer, Approval: Approval, } - #[derive(Drop, starknet::Event)] - struct Transfer { - from: ContractAddress, - to: ContractAddress, - value: felt252, + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct Transfer { + pub from: ContractAddress, + pub to: ContractAddress, + pub value: felt252, } - #[derive(Drop, starknet::Event)] - struct Approval { - owner: ContractAddress, - spender: ContractAddress, - value: felt252, + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct Approval { + pub owner: ContractAddress, + pub spender: ContractAddress, + pub value: felt252, } mod Errors { @@ -212,4 +212,341 @@ mod erc20 { } // ANCHOR_END: erc20 +#[cfg(test)] +mod tests { + use super::{erc20, IERC20Dispatcher, IERC20DispatcherTrait, erc20::{Event, Transfer, Approval}}; + use starknet::{ + ContractAddress, SyscallResultTrait, syscalls::deploy_syscall, get_caller_address, + contract_address_const + }; + use core::num::traits::Zero; + + use starknet::testing::{set_contract_address, set_account_contract_address}; + + const token_name: felt252 = 'myToken'; + const decimals: u8 = 18; + const initial_supply: felt252 = 100000; + const symbols: felt252 = 'mtk'; + + fn deploy() -> (IERC20Dispatcher, ContractAddress) { + let recipient: ContractAddress = contract_address_const::<'initialzed_recipient'>(); + + let (contract_address, _) = deploy_syscall( + erc20::TEST_CLASS_HASH.try_into().unwrap(), + recipient.into(), + array![recipient.into(), token_name, decimals.into(), initial_supply, symbols].span(), + false + ) + .unwrap_syscall(); + + (IERC20Dispatcher { contract_address }, contract_address) + } + + + #[test] + #[should_panic(expected: ('ERC20: mint to 0', 'CONSTRUCTOR_FAILED'))] + fn test_deploy_when_recipient_is_address_zero() { + let recipient: ContractAddress = Zero::zero(); + + let (_contract_address, _) = deploy_syscall( + erc20::TEST_CLASS_HASH.try_into().unwrap(), + recipient.into(), + array![recipient.into(), token_name, decimals.into(), initial_supply, symbols].span(), + false + ) + .unwrap_syscall(); + } + #[test] + fn test_deploy_success() { + let recipient = contract_address_const::<'initialzed_recipient'>(); + let (_, contract_address) = deploy(); + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer( + Transfer { from: Zero::zero(), to: recipient, value: initial_supply } + ) + ) + ); + } + + + #[test] + fn test_get_name() { + let (dispatcher, _) = deploy(); + let name = dispatcher.get_name(); + assert(name == token_name, 'wrong token name'); + } + + #[test] + fn test_get_symbol() { + let (dispatcher, _) = deploy(); + assert(dispatcher.get_symbol() == symbols, 'wrong symbol'); + } + + #[test] + fn test_get_decimals() { + let (dispatcher, _) = deploy(); + assert(dispatcher.get_decimals() == decimals, 'wrong decimals'); + } + + #[test] + fn test_total_supply() { + let (dispatcher, _) = deploy(); + assert(dispatcher.get_total_supply() == initial_supply, 'wrong total supply'); + } + + #[test] + fn test_balance_of_recipient_deployed() { + let recipient = contract_address_const::<'initialzed_recipient'>(); + let (dispatcher, _) = deploy(); + assert( + dispatcher.balance_of(recipient) == initial_supply, 'incorrect balance of recipient' + ); + } + + #[test] + fn test_allowance_without_approval() { + let caller = contract_address_const::<'caller'>(); + let spender = contract_address_const::<'spender'>(); + let (dispatcher, _) = deploy(); + set_contract_address(caller); + assert(dispatcher.allowance(caller, spender) == 0, 'incorrect allowance') + } + + #[test] + fn test_allowance_after_approval() { + let caller = contract_address_const::<'caller'>(); + let spender = contract_address_const::<'spender'>(); + let (dispatcher, _) = deploy(); + let amount = 100; + set_contract_address(caller); + dispatcher.approve(spender, amount); + assert(dispatcher.allowance(caller, spender) == amount, 'incorrect allowance') + } + + #[test] + #[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] + fn test_approval_spender_is_address_zero() { + let spender: ContractAddress = Zero::zero(); + let amount = 100; + let (dispatcher, _) = deploy(); + dispatcher.approve(spender, amount); + } + + #[test] + fn test_approval_success() { + let recipient = contract_address_const::<'initialzed_recipient'>(); + let spender = contract_address_const::<'spender'>(); + let value = 100; + let (dispatcher, contract_address) = deploy(); + let caller = contract_address_const::<'caller'>(); + set_contract_address(caller); + dispatcher.approve(spender, value); + set_contract_address(contract_address); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer( + Transfer { from: Zero::zero(), to: recipient, value: initial_supply } + ) + ) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Approval(Approval { owner: caller, spender, value })) + ); + } + + #[test] + #[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] + fn test_should_increase_allowance_with_spender_zero_address() { + let spender = Zero::zero(); + let amount = 100; + let (dispatcher, _) = deploy(); + dispatcher.increase_allowance(spender, amount); + } + + #[test] + fn test_should_increase_allowance() { + let caller = contract_address_const::<'caller'>(); + let recipient = contract_address_const::<'initialzed_recipient'>(); + let spender = contract_address_const::<'spender'>(); + let amount = 100; + let (dispatcher, contract_address) = deploy(); + set_contract_address(caller); + dispatcher.approve(spender, amount); + assert(dispatcher.allowance(caller, spender) == amount, 'incorrect allowance'); + set_contract_address(caller); + dispatcher.increase_allowance(spender, 100); + assert( + dispatcher.allowance(caller, spender) == amount + 100, 'incorrect increased allowance' + ); + + // emits one transfer event and two approval events + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer( + Transfer { from: Zero::zero(), to: recipient, value: initial_supply } + ) + ) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Approval(Approval { owner: caller, spender, value: amount })) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Approval(Approval { owner: caller, spender, value: amount + 100 })) + ); + } + + #[test] + #[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] + fn test_should_decrease_allowance_with_spender_zero_address() { + let spender = Zero::zero(); + let amount = 100; + let (dispatcher, _) = deploy(); + dispatcher.decrease_allowance(spender, amount); + } + + #[test] + fn test_should_decrease_allowance() { + let caller = contract_address_const::<'caller'>(); + let recipient = contract_address_const::<'initialzed_recipient'>(); + let spender = contract_address_const::<'spender'>(); + let amount = 100; + let (dispatcher, contract_address) = deploy(); + set_contract_address(caller); + dispatcher.approve(spender, amount); + assert(dispatcher.allowance(caller, spender) == amount, 'incorrect allowance'); + + set_contract_address(caller); + dispatcher.decrease_allowance(spender, 90); + assert( + dispatcher.allowance(caller, spender) == amount - 90, 'incorrect decreased allowance' + ); + + // emits one transfer event and two approval events + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer( + Transfer { from: Zero::zero(), to: recipient, value: initial_supply } + ) + ) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Approval(Approval { owner: caller, spender, value: amount })) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Approval(Approval { owner: caller, spender, value: amount - 90 })) + ); + } + + #[test] + #[should_panic(expected: ('ERC20: transfer from 0', 'ENTRYPOINT_FAILED'))] + fn test_transfer_when_sender_is_address_zero() { + let reciever = contract_address_const::<'spender'>(); + let amount = 100; + let (dispatcher, _) = deploy(); + dispatcher.transfer(reciever, amount); + } + + #[test] + #[should_panic(expected: ('ERC20: transfer to 0', 'ENTRYPOINT_FAILED'))] + #[should_panic] + fn test_transfer_when_recipient_is_address_zero() { + let caller = contract_address_const::<'caller'>(); + let reciever = Zero::zero(); + let amount = 100; + let (dispatcher, _) = deploy(); + set_contract_address(caller); + dispatcher.transfer(reciever, amount); + } + + #[test] + fn test_transfer_success() { + let caller = contract_address_const::<'initialzed_recipient'>(); + let reciever = contract_address_const::<'receiver'>(); + let amount = 100; + let (dispatcher, contract_address) = deploy(); + set_contract_address(caller); + dispatcher.transfer(reciever, amount); + assert_eq!(dispatcher.balance_of(reciever), amount); + + // emits two transfer events + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer(Transfer { from: Zero::zero(), to: caller, value: initial_supply }) + ) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Transfer(Transfer { from: caller, to: reciever, value: amount })) + ); + } + + + #[test] + #[should_panic(expected: ('ERC20: transfer from 0', 'ENTRYPOINT_FAILED'))] + #[should_panic] + fn test_transferFrom_when_sender_is_address_zero() { + let sender = Zero::zero(); + let amount = 100; + let reciever = contract_address_const::<'spender'>(); + let (dispatcher, _) = deploy(); + dispatcher.transfer_from(sender, reciever, amount); + } + + #[test] + #[should_panic(expected: ('ERC20: transfer to 0', 'ENTRYPOINT_FAILED'))] + #[should_panic] + fn test_transferFrom_when_recipient_is_address_zero() { + let caller = contract_address_const::<'caller'>(); + let reciever = Zero::zero(); + let amount = 100; + let (dispatcher, _) = deploy(); + set_contract_address(caller); + dispatcher.transfer_from(caller, reciever, amount); + } + + #[test] + fn test_transferFrom_success() { + let caller = contract_address_const::<'initialzed_recipient'>(); + let reciever = contract_address_const::<'receiver'>(); + let amount = 100; + let (dispatcher, contract_address) = deploy(); + set_contract_address(caller); + dispatcher.transfer_from(caller, reciever, amount); + assert_eq!(dispatcher.balance_of(reciever), amount); + + // emits two transfer events + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer(Transfer { from: Zero::zero(), to: caller, value: initial_supply }) + ) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Transfer(Transfer { from: caller, to: reciever, value: amount })) + ); + } +}