diff --git a/token/src/components/tests.cairo b/token/src/components/tests.cairo index f4045a19..309082bc 100644 --- a/token/src/components/tests.cairo +++ b/token/src/components/tests.cairo @@ -1,7 +1,20 @@ mod mocks { mod initializable_mock; + mod erc20_allowance_mock; + mod erc20_balance_mock; + mod erc20_metadata_mock; } + mod security { #[cfg(test)] mod test_initializable; } + +mod token { + #[cfg(test)] + mod test_erc20_allowance; + #[cfg(test)] + mod test_erc20_balance; + #[cfg(test)] + mod test_erc20_metadata; +} diff --git a/token/src/components/tests/mocks/erc20_allowance_mock.cairo b/token/src/components/tests/mocks/erc20_allowance_mock.cairo new file mode 100644 index 00000000..927cb301 --- /dev/null +++ b/token/src/components/tests/mocks/erc20_allowance_mock.cairo @@ -0,0 +1,31 @@ +#[dojo::contract] +mod ERC20AllowanceMock { + use token::components::token::erc20_allowance::ERC20AllowanceComponent; + + component!(path: ERC20AllowanceComponent, storage: erc20_allowance, event: ERC20AllowanceEvent); + + #[abi(embed_v0)] + impl ERC20AllowanceImpl = + ERC20AllowanceComponent::ERC20AllowanceImpl; + #[abi(embed_v0)] + impl ERC20SafeAllowanceImpl = + ERC20AllowanceComponent::ERC20SafeAllowanceImpl; + #[abi(embed_v0)] + impl ERC20SafeAllowanceCamelImpl = + ERC20AllowanceComponent::ERC20SafeAllowanceCamelImpl; + + impl ERC20AllowanceInternalImpl = ERC20AllowanceComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20_allowance: ERC20AllowanceComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20AllowanceEvent: ERC20AllowanceComponent::Event + } +} diff --git a/token/src/components/tests/mocks/erc20_balance_mock.cairo b/token/src/components/tests/mocks/erc20_balance_mock.cairo new file mode 100644 index 00000000..9db0c553 --- /dev/null +++ b/token/src/components/tests/mocks/erc20_balance_mock.cairo @@ -0,0 +1,24 @@ +#[dojo::contract] +mod ERC20BalanceMock { + use token::components::token::erc20_balance::ERC20BalanceComponent; + + component!(path: ERC20BalanceComponent, storage: erc20_balance, event: ERC20BalanceEvent); + + #[abi(embed_v0)] + impl ERC20BalanceImpl = ERC20BalanceComponent::ERC20BalanceImpl; + + impl ERC20BalanceInternalImpl = ERC20BalanceComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20_balance: ERC20BalanceComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20BalanceEvent: ERC20BalanceComponent::Event + } +} diff --git a/token/src/components/tests/mocks/erc20_metadata_mock.cairo b/token/src/components/tests/mocks/erc20_metadata_mock.cairo new file mode 100644 index 00000000..93288277 --- /dev/null +++ b/token/src/components/tests/mocks/erc20_metadata_mock.cairo @@ -0,0 +1,25 @@ +#[dojo::contract] +mod ERC20MetadataMock { + use token::components::token::erc20_metadata::ERC20MetadataComponent; + + component!(path: ERC20MetadataComponent, storage: erc20_metadata, event: ERC20MetadataEvent); + + #[abi(embed_v0)] + impl ERC20MetadataImpl = + ERC20MetadataComponent::ERC20MetadataImpl; + + impl ERC20MetadataInternalImpl = ERC20MetadataComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20_metadata: ERC20MetadataComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20MetadataEvent: ERC20MetadataComponent::Event + } +} diff --git a/token/src/components/tests/token/test_erc20_allowance.cairo b/token/src/components/tests/token/test_erc20_allowance.cairo new file mode 100644 index 00000000..078ab042 --- /dev/null +++ b/token/src/components/tests/token/test_erc20_allowance.cairo @@ -0,0 +1,28 @@ +use integer::BoundedInt; +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; +use dojo::test_utils::spawn_test_world; +use token::tests::constants::{ADMIN, OWNER, SPENDER}; + +use token::components::token::erc20_allowance::{erc_20_allowance_model, ERC20AllowanceModel,}; +use token::components::token::erc20_allowance::ERC20AllowanceComponent::{ + ERC20AllowanceImpl, InternalImpl +}; +use token::components::tests::mocks::erc20_allowance_mock::ERC20AllowanceMock; +use token::components::tests::mocks::erc20_allowance_mock::ERC20AllowanceMock::world_dispatcherContractMemberStateTrait; + + +fn STATE() -> (IWorldDispatcher, ERC20AllowanceMock::ContractState) { + let world = spawn_test_world(array![erc_20_allowance_model::TEST_CLASS_HASH,]); + + let mut state = ERC20AllowanceMock::contract_state_for_testing(); + state.world_dispatcher.write(world); + + (world, state) +} + +#[test] +#[available_gas(100000000)] +fn test_erc20_allowance_initialize() { + let (world, mut state) = STATE(); +// state.erc20_allowance +} diff --git a/token/src/components/tests/token/test_erc20_balance.cairo b/token/src/components/tests/token/test_erc20_balance.cairo new file mode 100644 index 00000000..dc808e87 --- /dev/null +++ b/token/src/components/tests/token/test_erc20_balance.cairo @@ -0,0 +1,101 @@ +use integer::BoundedInt; +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; +use dojo::test_utils::spawn_test_world; +use token::tests::constants::{ADMIN, ZERO, OWNER, OTHER}; + +use token::components::token::erc20_balance::{erc_20_balance_model, ERC20BalanceModel,}; +use token::components::token::erc20_balance::ERC20BalanceComponent::{ + ERC20BalanceImpl, InternalImpl +}; +use token::components::tests::mocks::erc20_balance_mock::ERC20BalanceMock; +use token::components::tests::mocks::erc20_balance_mock::ERC20BalanceMock::world_dispatcherContractMemberStateTrait; + + +fn STATE() -> (IWorldDispatcher, ERC20BalanceMock::ContractState) { + let world = spawn_test_world(array![erc_20_balance_model::TEST_CLASS_HASH,]); + + let mut state = ERC20BalanceMock::contract_state_for_testing(); + state.world_dispatcher.write(world); + + (world, state) +} + +#[test] +#[available_gas(100000000)] +fn test_erc20_balance_initialize() { + let (world, mut state) = STATE(); + + assert(state.erc20_balance.balance_of(ADMIN()) == 0, 'Should be 0'); + assert(state.erc20_balance.balance_of(OWNER()) == 0, 'Should be 0'); + assert(state.erc20_balance.balance_of(OTHER()) == 0, 'Should be 0'); +} + +#[test] +#[available_gas(100000000)] +fn test_erc20_balance__update_balance() { + let (world, mut state) = STATE(); + + state.erc20_balance._update_balance(ZERO(), 0, 420); + assert(state.erc20_balance.balance_of(ZERO()) == 420, 'Should be 420'); + + state.erc20_balance._update_balance(ZERO(), 0, 1000); + assert(state.erc20_balance.balance_of(ZERO()) == 1420, 'Should be 1420'); + + state.erc20_balance._update_balance(ZERO(), 420, 0); + assert(state.erc20_balance.balance_of(ZERO()) == 1000, 'Should be 1000'); +} + +#[test] +#[available_gas(10000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_erc20_balance__update_balance_sub_overflow() { + let (world, mut state) = STATE(); + + state.erc20_balance._update_balance(ZERO(), 1, 0); +} + +#[test] +#[available_gas(10000000)] +#[should_panic(expected: ('u256_add Overflow',))] +fn test_erc20_balance__update_balance_add_overflow() { + let (world, mut state) = STATE(); + + state.erc20_balance._update_balance(ZERO(), 0, BoundedInt::max()); + state.erc20_balance._update_balance(ZERO(), 0, 1); +} + + +#[test] +#[available_gas(100000000)] +fn test_erc20_balance__transfer() { + let (world, mut state) = STATE(); + + state.erc20_balance._update_balance(ADMIN(), 0, 420); + state.erc20_balance._update_balance(OTHER(), 0, 1000); + + state.erc20_balance._transfer(ADMIN(), OTHER(), 100); + assert(state.erc20_balance.balance_of(ADMIN()) == 320, 'Should be 320'); + assert(state.erc20_balance.balance_of(OTHER()) == 1100, 'Should be 1100'); + + state.erc20_balance._transfer(OTHER(), ADMIN(), 1000); + assert(state.erc20_balance.balance_of(ADMIN()) == 1320, 'Should be 1320'); + assert(state.erc20_balance.balance_of(OTHER()) == 100, 'Should be 100'); +} + +#[test] +#[available_gas(100000000)] +#[should_panic(expected: ('ERC20: transfer from 0',))] +fn test_erc20_balance__transfer_from_zero() { + let (world, mut state) = STATE(); + + state.erc20_balance._transfer(ZERO(), ADMIN(), 420); +} + +#[test] +#[available_gas(100000000)] +#[should_panic(expected: ('ERC20: transfer to 0',))] +fn test_erc20_balance__transfer_to_zero() { + let (world, mut state) = STATE(); + + state.erc20_balance._transfer(ADMIN(), ZERO(), 420); +} diff --git a/token/src/components/tests/token/test_erc20_metadata.cairo b/token/src/components/tests/token/test_erc20_metadata.cairo new file mode 100644 index 00000000..00806a96 --- /dev/null +++ b/token/src/components/tests/token/test_erc20_metadata.cairo @@ -0,0 +1,70 @@ +use integer::BoundedInt; +use dojo::world::{IWorldDispatcher, IWorldDispatcherTrait}; +use dojo::test_utils::spawn_test_world; +use token::tests::constants::{NAME, SYMBOL, DECIMALS}; + +use token::components::token::erc20_metadata::{erc_20_metadata_model, ERC20MetadataModel,}; +use token::components::token::erc20_metadata::ERC20MetadataComponent::{ + ERC20MetadataImpl, InternalImpl +}; +use token::components::tests::mocks::erc20_metadata_mock::ERC20MetadataMock; +use token::components::tests::mocks::erc20_metadata_mock::ERC20MetadataMock::world_dispatcherContractMemberStateTrait; + + +fn STATE() -> (IWorldDispatcher, ERC20MetadataMock::ContractState) { + let world = spawn_test_world(array![erc_20_metadata_model::TEST_CLASS_HASH,]); + + let mut state = ERC20MetadataMock::contract_state_for_testing(); + state.world_dispatcher.write(world); + + (world, state) +} + +#[test] +#[available_gas(100000000)] +fn test_erc20_metadata__initialize() { + let (world, mut state) = STATE(); + + state.erc20_metadata._initialize(NAME, SYMBOL, DECIMALS); + + assert(state.erc20_metadata.name() == NAME, 'Should be NAME'); + assert(state.erc20_metadata.symbol() == SYMBOL, 'Should be SYMBOL'); + assert(state.erc20_metadata.decimals() == DECIMALS, 'Should be 18'); + assert(state.erc20_metadata.total_supply() == 0, 'Should be 0'); +} + +#[test] +#[available_gas(100000000)] +fn test_erc20_metadata__update_total_supply() { + let (world, mut state) = STATE(); + + state.erc20_metadata._update_total_supply(0, 420); + assert(state.erc20_metadata.total_supply() == 420, 'Should be 420'); + + state.erc20_metadata._update_total_supply(0, 1000); + assert(state.erc20_metadata.total_supply() == 1420, 'Should be 1420'); + + state.erc20_metadata._update_total_supply(420, 0); + assert(state.erc20_metadata.total_supply() == 1000, 'Should be 1000'); +} + + +#[test] +#[available_gas(10000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_erc20_metadata__update_total_supply_sub_overflow() { + let (world, mut state) = STATE(); + + state.erc20_metadata._update_total_supply(1, 0); +} + + +#[test] +#[available_gas(10000000)] +#[should_panic(expected: ('u256_add Overflow',))] +fn test_erc20_metadata__update_total_supply_add_overflow() { + let (world, mut state) = STATE(); + + state.erc20_metadata._update_total_supply(0, BoundedInt::max()); + state.erc20_metadata._update_total_supply(0, 1); +} diff --git a/token/src/components/token/erc20_allowance.cairo b/token/src/components/token/erc20_allowance.cairo new file mode 100644 index 00000000..73239736 --- /dev/null +++ b/token/src/components/token/erc20_allowance.cairo @@ -0,0 +1,211 @@ +use starknet::ContractAddress; + +/// +/// Model +/// + +#[derive(Model, Copy, Drop, Serde)] +struct ERC20AllowanceModel { + #[key] + token: ContractAddress, + #[key] + owner: ContractAddress, + #[key] + spender: ContractAddress, + amount: u256, +} + +/// +/// Interface +/// + +#[starknet::interface] +trait IERC20Allowance { + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} + +#[starknet::interface] +trait IERC20SafeAllowance { + fn increase_allowance(ref self: TState, spender: ContractAddress, added_value: u256) -> bool; + fn decrease_allowance( + ref self: TState, spender: ContractAddress, subtracted_value: u256 + ) -> bool; +} + +#[starknet::interface] +trait IERC20SafeAllowanceCamel { + fn increaseAllowance(ref self: TState, spender: ContractAddress, addedValue: u256) -> bool; + fn decreaseAllowance(ref self: TState, spender: ContractAddress, subtractedValue: u256) -> bool; +} + + +/// ERC20Allowance Component +/// +/// TODO: desc +#[starknet::component] +mod ERC20AllowanceComponent { + use super::ERC20AllowanceModel; + use super::IERC20Allowance; + use super::IERC20SafeAllowance; + use super::IERC20SafeAllowanceCamel; + use integer::BoundedInt; + use starknet::ContractAddress; + use starknet::{get_contract_address, get_caller_address}; + use dojo::world::{ + IWorldProvider, IWorldProviderDispatcher, IWorldDispatcher, IWorldDispatcherTrait + }; + + #[storage] + struct Storage {} + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Approval: Approval + } + + #[derive(Copy, Drop, Serde, starknet::Event)] + struct Approval { + owner: ContractAddress, + spender: ContractAddress, + value: u256 + } + + mod Errors { + const APPROVE_FROM_ZERO: felt252 = 'ERC20: approve from 0'; + const APPROVE_TO_ZERO: felt252 = 'ERC20: approve to 0'; + } + + #[embeddable_as(ERC20AllowanceImpl)] + impl ERC20Allowance< + TContractState, +HasComponent, +IWorldProvider, + > of IERC20Allowance> { + fn allowance( + self: @ComponentState, owner: ContractAddress, spender: ContractAddress + ) -> u256 { + self.get_allowance(owner, spender).amount + } + fn approve( + ref self: ComponentState, spender: ContractAddress, amount: u256 + ) -> bool { + let owner = get_caller_address(); + self + .set_allowance( + ERC20AllowanceModel { token: get_contract_address(), owner, spender, amount } + ); + true + } + } + + #[embeddable_as(ERC20SafeAllowanceImpl)] + impl ERC20SafeAllowance< + TContractState, +HasComponent, +IWorldProvider, + > of IERC20SafeAllowance> { + fn increase_allowance( + ref self: ComponentState, spender: ContractAddress, added_value: u256 + ) -> bool { + self.update_allowance(get_caller_address(), spender, 0, added_value); + true + } + + fn decrease_allowance( + ref self: ComponentState, + spender: ContractAddress, + subtracted_value: u256 + ) -> bool { + self.update_allowance(get_caller_address(), spender, subtracted_value, 0); + true + } + } + + #[embeddable_as(ERC20SafeAllowanceCamelImpl)] + impl ERC20SafeAllowanceCamel< + TContractState, +HasComponent, +IWorldProvider, + > of IERC20SafeAllowanceCamel> { + fn increaseAllowance( + ref self: ComponentState, spender: ContractAddress, addedValue: u256 + ) -> bool { + self.increase_allowance(spender, addedValue) + } + + fn decreaseAllowance( + ref self: ComponentState, + spender: ContractAddress, + subtractedValue: u256 + ) -> bool { + self.decrease_allowance(spender, subtractedValue) + } + } + + /// + /// Internal + /// + + #[generate_trait] + impl InternalImpl< + TContractState, +HasComponent, +IWorldProvider, + > of InternalTrait { + // Helper function for allowance model + fn get_allowance( + self: @ComponentState, owner: ContractAddress, spender: ContractAddress, + ) -> ERC20AllowanceModel { + get!( + self.get_contract().world(), + (get_contract_address(), owner, spender), + ERC20AllowanceModel + ) + } + + + fn update_allowance( + ref self: ComponentState, + owner: ContractAddress, + spender: ContractAddress, + subtract: u256, + add: u256 + ) { + let mut allowance = self.get_allowance(owner, spender); + // adding and subtracting is fewer steps than if + allowance.amount = allowance.amount - subtract; + allowance.amount = allowance.amount + add; + self.set_allowance(allowance); + } + + fn set_allowance(ref self: ComponentState, allowance: ERC20AllowanceModel) { + assert(!allowance.owner.is_zero(), Errors::APPROVE_FROM_ZERO); + assert(!allowance.spender.is_zero(), Errors::APPROVE_TO_ZERO); + set!(self.get_contract().world(), (allowance)); + // self + // .emit_event( + // Approval { + // owner: allowance.owner, spender: allowance.spender, value: allowance.amount + // } + // ); + } + + fn _approve( + ref self: ComponentState, + owner: ContractAddress, + spender: ContractAddress, + amount: u256 + ) { + self + .set_allowance( + ERC20AllowanceModel { token: get_contract_address(), owner, spender, amount } + ); + } + + fn _spend_allowance( + ref self: ComponentState, + owner: ContractAddress, + spender: ContractAddress, + amount: u256 + ) { + let current_allowance = self.get_allowance(owner, spender).amount; + if current_allowance != BoundedInt::max() { + self.update_allowance(owner, spender, amount, 0); + } + } + } +} diff --git a/token/src/components/token/erc20_balance.cairo b/token/src/components/token/erc20_balance.cairo new file mode 100644 index 00000000..00944223 --- /dev/null +++ b/token/src/components/token/erc20_balance.cairo @@ -0,0 +1,116 @@ +use starknet::ContractAddress; + +/// +/// Model +/// + +#[derive(Model, Copy, Drop, Serde)] +struct ERC20BalanceModel { + #[key] + token: ContractAddress, + #[key] + account: ContractAddress, + amount: u256, +} + +/// +/// Interface +/// + +#[starknet::interface] +trait IERC20Balance { + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; +} + + +/// ERC20Balance Component +/// +/// TODO: desc +#[starknet::component] +mod ERC20BalanceComponent { + use super::ERC20BalanceModel; + use super::IERC20Balance; + use starknet::ContractAddress; + use starknet::{get_contract_address, get_caller_address}; + use dojo::world::{ + IWorldProvider, IWorldProviderDispatcher, IWorldDispatcher, IWorldDispatcherTrait + }; + + #[storage] + struct Storage {} + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Transfer: Transfer + } + + #[derive(Drop, Serde, starknet::Event)] + struct Transfer { + from: ContractAddress, + to: ContractAddress, + value: u256 + } + + mod Errors { + const TRANSFER_FROM_ZERO: felt252 = 'ERC20: transfer from 0'; + const TRANSFER_TO_ZERO: felt252 = 'ERC20: transfer to 0'; + } + + #[embeddable_as(ERC20BalanceImpl)] + impl ERC20Balance< + TContractState, +HasComponent, +IWorldProvider, + > of IERC20Balance> { + fn balance_of(self: @ComponentState, account: ContractAddress) -> u256 { + self.get_balance(account).amount + } + fn transfer( + ref self: ComponentState, recipient: ContractAddress, amount: u256 + ) -> bool { + let sender = get_caller_address(); + self._transfer(sender, recipient, amount); + true + } + } + + #[generate_trait] + impl InternalImpl< + TContractState, +HasComponent, +IWorldProvider, + > of InternalTrait { + fn get_balance( + self: @ComponentState, account: ContractAddress + ) -> ERC20BalanceModel { + get!( + self.get_contract().world(), (get_contract_address(), account), (ERC20BalanceModel) + ) + } + + fn _update_balance( + ref self: ComponentState, + account: ContractAddress, + subtract: u256, + add: u256 + ) { + let mut balance = self.get_balance(account); + // adding and subtracting is fewer steps than if + balance.amount = balance.amount - subtract; + balance.amount = balance.amount + add; + set!(self.get_contract().world(), (balance)); + } + + fn _transfer( + ref self: ComponentState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO); + assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO); + self._update_balance(sender, amount, 0); + self._update_balance(recipient, 0, amount); + // self.emit(Transfer { from: sender, to: recipient, value: amount }); + // emit!(self.get_contract().world(), Transfer { from: sender, to: recipient, value: amount }); + } + } +} diff --git a/token/src/components/token/erc20_metadata.cairo b/token/src/components/token/erc20_metadata.cairo index 69914ffc..2896cdc0 100644 --- a/token/src/components/token/erc20_metadata.cairo +++ b/token/src/components/token/erc20_metadata.cairo @@ -33,6 +33,8 @@ trait IERC20Metadata { mod ERC20MetadataComponent { use super::ERC20MetadataModel; use super::IERC20Metadata; + + use starknet::get_contract_address; use dojo::world::{ IWorldProvider, IWorldProviderDispatcher, IWorldDispatcher, IWorldDispatcherTrait @@ -43,7 +45,7 @@ mod ERC20MetadataComponent { #[embeddable_as(ERC20MetadataImpl)] impl ERC20Metadata< - TContractState, +HasComponent, +IWorldProvider + TContractState, +HasComponent, +IWorldProvider, > of IERC20Metadata> { fn name(self: @ComponentState) -> felt252 { self.get_metadata().name @@ -60,7 +62,7 @@ mod ERC20MetadataComponent { impl InternalImpl< TContractState, +HasComponent, +IWorldProvider > of InternalTrait { - fn initialize( + fn _initialize( ref self: ComponentState, name: felt252, symbol: felt252, decimals: u8 ) { set!( @@ -80,11 +82,11 @@ mod ERC20MetadataComponent { } // Helper function to update total_supply model - fn update_total_supply( + fn _update_total_supply( ref self: ComponentState, subtract: u256, add: u256 ) { let mut meta = self.get_metadata(); - // // adding and subtracting is fewer steps than if + // adding and subtracting is fewer steps than if meta.total_supply = meta.total_supply - subtract; meta.total_supply = meta.total_supply + add; set!(self.get_contract().world(), (meta)); diff --git a/token/src/components/utility/event_emitter.cairo b/token/src/components/utility/event_emitter.cairo new file mode 100644 index 00000000..7ce1ce83 --- /dev/null +++ b/token/src/components/utility/event_emitter.cairo @@ -0,0 +1,36 @@ +// /// Event emitter Component +// /// +// /// Emit an event for both token contract & dojo world +// #[starknet::component] +// mod EventEmitterComponent { +// //use super::IEventEmitter; +// use starknet::get_contract_address; +// use starknet::event::EventEmitter; +// use dojo::world::{ +// IWorldProvider, IWorldProviderDispatcher, IWorldDispatcher, IWorldDispatcherTrait +// }; + +// #[storage] +// struct Storage {} + +// #[event] +// #[derive(Drop, Copy, Serde, starknet::Event)] +// enum Event {} + +// #[generate_trait] +// impl InternalImpl< +// TContractState, +HasComponent, +IWorldProvider, +// > of InternalTrait { +// fn emit_event, +Drop>( +// ref self: ComponentState, event: S +// ) { +// // not ok :( +// //let mut contract_mut = self.get_contract_mut(); +// //starknet::event::EventEmitter::emit(ref contract_mut, event); +// //contract_mut.emit(event); +// self.emit(event); +// emit!(self.get_contract().world(), event); +// } +// } +// } + diff --git a/token/src/lib.cairo b/token/src/lib.cairo index ef91b3a6..29fa24ec 100644 --- a/token/src/lib.cairo +++ b/token/src/lib.cairo @@ -4,9 +4,15 @@ mod components { } mod token { + mod erc20_allowance; + mod erc20_balance; mod erc20_metadata; } + // mod utility { + // mod event_emitter; + // } + mod tests; } diff --git a/token/src/preset/erc20.cairo b/token/src/preset/erc20.cairo index 6e97dc94..6a1ddab0 100644 --- a/token/src/preset/erc20.cairo +++ b/token/src/preset/erc20.cairo @@ -1,17 +1,22 @@ #[dojo::contract] mod ERC20 { - use token::erc20::models::{ERC20Allowance, ERC20Balance, ERC20Meta}; use token::erc20::interface; use integer::BoundedInt; use starknet::ContractAddress; use starknet::{get_caller_address, get_contract_address}; use zeroable::Zeroable; - use token::components::token::erc20_metadata::ERC20MetadataComponent; use token::components::security::initializable::InitializableComponent; + use token::components::token::erc20_metadata::ERC20MetadataComponent; + use token::components::token::erc20_balance::ERC20BalanceComponent; + use token::components::token::erc20_allowance::ERC20AllowanceComponent; + component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); + component!(path: ERC20MetadataComponent, storage: erc20_metadata, event: ERC20MetadataEvent); + component!(path: ERC20BalanceComponent, storage: erc20_balance, event: ERC20BalanceEvent); + component!(path: ERC20AllowanceComponent, storage: erc20_allowance, event: ERC20AllowanceEvent); #[storage] struct Storage { @@ -19,53 +24,53 @@ mod ERC20 { initializable: InitializableComponent::Storage, #[substorage(v0)] erc20_metadata: ERC20MetadataComponent::Storage, + #[substorage(v0)] + erc20_balance: ERC20BalanceComponent::Storage, + #[substorage(v0)] + erc20_allowance: ERC20AllowanceComponent::Storage, } #[event] #[derive(Copy, Drop, starknet::Event)] enum Event { - Transfer: Transfer, - Approval: Approval, #[flat] InitializableEvent: InitializableComponent::Event, #[flat] ERC20MetadataEvent: ERC20MetadataComponent::Event, - } - - #[derive(Copy, Drop, starknet::Event)] - struct Transfer { - from: ContractAddress, - to: ContractAddress, - value: u256 - } - - #[derive(Copy, Drop, starknet::Event)] - struct Approval { - owner: ContractAddress, - spender: ContractAddress, - value: u256 + #[flat] + ERC20BalanceEvent: ERC20BalanceComponent::Event, + #[flat] + ERC20AllowanceEvent: ERC20AllowanceComponent::Event, } mod Errors { const ALREADY_INITIALIZED: felt252 = 'ERC20: already initialized'; const CALLER_IS_NOT_OWNER: felt252 = 'ERC20: caller is not owner'; - const APPROVE_FROM_ZERO: felt252 = 'ERC20: approve from 0'; - const APPROVE_TO_ZERO: felt252 = 'ERC20: approve to 0'; - const TRANSFER_FROM_ZERO: felt252 = 'ERC20: transfer from 0'; - const TRANSFER_TO_ZERO: felt252 = 'ERC20: transfer to 0'; const BURN_FROM_ZERO: felt252 = 'ERC20: burn from 0'; const MINT_TO_ZERO: felt252 = 'ERC20: mint to 0'; } - #[abi(embed_v0)] - impl InitializableImpl = - InitializableComponent::InitializableImpl; + impl InitializableImpl = InitializableComponent::InitializableImpl; #[abi(embed_v0)] impl ERC20MetadataImpl = ERC20MetadataComponent::ERC20MetadataImpl; + // #[abi(embed_v0)] + impl ERC20BalanceImpl = ERC20BalanceComponent::ERC20BalanceImpl; + + //#[abi(embed_v0)] + impl ERC20AllowanceImpl = ERC20AllowanceComponent::ERC20AllowanceImpl; + + #[abi(embed_v0)] + impl ERC20SafeAllowanceImpl = + ERC20AllowanceComponent::ERC20SafeAllowanceImpl; + + #[abi(embed_v0)] + impl ERC20SafeAllowanceCamelImpl = + ERC20AllowanceComponent::ERC20SafeAllowanceCamelImpl; + #[external(v0)] fn initializer( @@ -81,7 +86,7 @@ mod ERC20 { Errors::CALLER_IS_NOT_OWNER ); - self.erc20_metadata.initialize(name, symbol, 18); + self.erc20_metadata._initialize(name, symbol, 18); self._mint(recipient, initial_supply); self.initializable.initialize(); @@ -90,23 +95,21 @@ mod ERC20 { #[external(v0)] impl ERC20Impl of interface::IERC20 { fn total_supply(self: @ContractState) -> u256 { - self.erc20_metadata.get_metadata().total_supply + self.erc20_metadata.total_supply() } fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { - self.get_balance(account).amount + self.erc20_balance.balance_of(account) } fn allowance( self: @ContractState, owner: ContractAddress, spender: ContractAddress ) -> u256 { - self.get_allowance(owner, spender).amount + self.erc20_allowance.allowance(owner, spender) } fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { - let sender = get_caller_address(); - self._transfer(sender, recipient, amount); - true + self.erc20_balance.transfer(recipient, amount) } fn transfer_from( @@ -116,18 +119,13 @@ mod ERC20 { amount: u256 ) -> bool { let caller = get_caller_address(); - self._spend_allowance(sender, caller, amount); - self._transfer(sender, recipient, amount); + self.erc20_allowance._spend_allowance(sender, caller, amount); + self.erc20_balance._transfer(sender, recipient, amount); true } fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { - let owner = get_caller_address(); - self - .set_allowance( - ERC20Allowance { token: get_contract_address(), owner, spender, amount } - ); - true + self.erc20_allowance.approve(spender, amount) } } @@ -151,35 +149,6 @@ mod ERC20 { } } - #[external(v0)] - fn increase_allowance( - ref self: ContractState, spender: ContractAddress, added_value: u256 - ) -> bool { - self.update_allowance(get_caller_address(), spender, 0, added_value); - true - } - - #[external(v0)] - fn increaseAllowance( - ref self: ContractState, spender: ContractAddress, addedValue: u256 - ) -> bool { - increase_allowance(ref self, spender, addedValue) - } - - #[external(v0)] - fn decrease_allowance( - ref self: ContractState, spender: ContractAddress, subtracted_value: u256 - ) -> bool { - self.update_allowance(get_caller_address(), spender, subtracted_value, 0); - true - } - - #[external(v0)] - fn decreaseAllowance( - ref self: ContractState, spender: ContractAddress, subtractedValue: u256 - ) -> bool { - decrease_allowance(ref self, spender, subtractedValue) - } // // Internal @@ -187,57 +156,11 @@ mod ERC20 { impl InitializableInternalImpl = InitializableComponent::InternalImpl; impl ERC20MetadataInternalImpl = ERC20MetadataComponent::InternalImpl; + impl ERC20BalanceInternalImpl = ERC20BalanceComponent::InternalImpl; + impl ERC20AllowanceInternalImpl = ERC20AllowanceComponent::InternalImpl; #[generate_trait] impl WorldInteractionsImpl of WorldInteractionsTrait { - // Helper function for balance model - fn get_balance(self: @ContractState, account: ContractAddress) -> ERC20Balance { - get!(self.world(), (get_contract_address(), account), ERC20Balance) - } - - fn update_balance( - ref self: ContractState, account: ContractAddress, subtract: u256, add: u256 - ) { - let mut balance: ERC20Balance = self.get_balance(account); - // adding and subtracting is fewer steps than if - balance.amount = balance.amount - subtract; - balance.amount = balance.amount + add; - set!(self.world(), (balance)); - } - - // Helper function for allowance model - fn get_allowance( - self: @ContractState, owner: ContractAddress, spender: ContractAddress, - ) -> ERC20Allowance { - get!(self.world(), (get_contract_address(), owner, spender), ERC20Allowance) - } - - fn update_allowance( - ref self: ContractState, - owner: ContractAddress, - spender: ContractAddress, - subtract: u256, - add: u256 - ) { - let mut allowance = self.get_allowance(owner, spender); - // adding and subtracting is fewer steps than if - allowance.amount = allowance.amount - subtract; - allowance.amount = allowance.amount + add; - self.set_allowance(allowance); - } - - fn set_allowance(ref self: ContractState, allowance: ERC20Allowance) { - assert(!allowance.owner.is_zero(), Errors::APPROVE_FROM_ZERO); - assert(!allowance.spender.is_zero(), Errors::APPROVE_TO_ZERO); - set!(self.world(), (allowance)); - self - .emit_event( - Approval { - owner: allowance.owner, spender: allowance.spender, value: allowance.amount - } - ); - } - fn emit_event< S, impl IntoImp: traits::Into, impl SDrop: Drop, impl SCopy: Copy >( @@ -248,51 +171,42 @@ mod ERC20 { } } + #[generate_trait] impl InternalImpl of InternalTrait { fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { assert(!recipient.is_zero(), Errors::MINT_TO_ZERO); - self.erc20_metadata.update_total_supply(0, amount); - self.update_balance(recipient, 0, amount); - self.emit_event(Transfer { from: Zeroable::zero(), to: recipient, value: amount }); - } + self.erc20_metadata._update_total_supply(0, amount); + self.erc20_balance._update_balance(recipient, 0, amount); + //self.emit_event(Transfer { from: Zeroable::zero(), to: recipient, value: amount }); - fn _burn(ref self: ContractState, account: ContractAddress, amount: u256) { - assert(!account.is_zero(), Errors::BURN_FROM_ZERO); - self.erc20_metadata.update_total_supply(amount, 0); - self.update_balance(account, amount, 0); - self.emit_event(Transfer { from: account, to: Zeroable::zero(), value: amount }); - } + // self + // .emit_event( + // ERC20BalanceComponent::Transfer { + // from: Zeroable::zero(), to: recipient, value: amount + // } + // ); - fn _approve( - ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 - ) { - self - .set_allowance( - ERC20Allowance { token: get_contract_address(), owner, spender, amount } - ); + // self.erc20_balance + // .emit( + // ERC20BalanceComponent::Transfer { + // from: Zeroable::zero(), to: recipient, value: amount + // } + // ); } - fn _transfer( - ref self: ContractState, - sender: ContractAddress, - recipient: ContractAddress, - amount: u256 - ) { - assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO); - assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO); - self.update_balance(sender, amount, 0); - self.update_balance(recipient, 0, amount); - self.emit_event(Transfer { from: sender, to: recipient, value: amount }); - } - - fn _spend_allowance( - ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 - ) { - let current_allowance = self.get_allowance(owner, spender).amount; - if current_allowance != BoundedInt::max() { - self.update_allowance(owner, spender, amount, 0); - } + fn _burn(ref self: ContractState, account: ContractAddress, amount: u256) { + assert(!account.is_zero(), Errors::BURN_FROM_ZERO); + self.erc20_metadata._update_total_supply(amount, 0); + self.erc20_balance._update_balance(account, amount, 0); + //self.emit_event(Transfer { from: account, to: Zeroable::zero(), value: amount }); + + // self.erc20_balance + // .emit( + // ERC20BalanceComponent::Transfer { + // from: account, to: Zeroable::zero(), value: amount + // } + // ); } } }