diff --git a/docs/modules/ROOT/pages/api/erc20.adoc b/docs/modules/ROOT/pages/api/erc20.adoc index 01f72937b..3e4ad3d30 100644 --- a/docs/modules/ROOT/pages/api/erc20.adoc +++ b/docs/modules/ROOT/pages/api/erc20.adoc @@ -168,7 +168,7 @@ use openzeppelin::token::erc20::ERC20Component; ``` ERC20 component extending <> and <>. -[.contract-index] +[.contract-index#ERC20Component-Embeddable-Impls] .Embeddable implementations -- .ERC20Impl @@ -189,7 +189,7 @@ ERC20 component extending <> and < * xref:#ERC20Component-decrease_allowance[`++decrease_allowance(self, spender, subtracted_value)++`] -- -[.contract-index] +[.contract-index#ERC20Component-Embeddable-Impls-camelCase] .Embeddable implementations (camelCase) -- .ERC20CamelOnlyImpl @@ -471,3 +471,43 @@ See <>. ==== `[.contract-item-name]#++Approval++#++(owner: ContractAddress, spender: ContractAddress, value: u256)++` [.item-kind]#event# See <>. + +== Presets + +[.contract] +[[ERC20]] +=== `++ERC20++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.8.0-beta.0/src/presets/erc20.cairo[{github-icon},role=heading-link] + +```javascript +use openzeppelin::presets::ERC20; +``` + +Basic ERC20 contract leveraging xref:#ERC20Component[ERC20Component] with a fixed-supply mechanism for token distribution. + +[.contract-index] +.Constructor +-- +* xref:#ERC20-constructor[`++constructor(self, name, symbol, fixed_supply, recipient)++`] +-- + +[.contract-index] +.Embedded Implementations +-- +.ERC20Component + +* xref:#ERC20Component-Embeddable-Impls[`++ERC20Impl++`] +* xref:#ERC20Component-Embeddable-Impls[`++ERC20MetadataImpl++`] +* xref:#ERC20Component-Embeddable-Impls[`++SafeAllowanceImpl++`] +* xref:#ERC20Component-Embeddable-Impls-camelCase[`++ERC20CamelOnlyImpl++`] +* xref:#ERC20Component-Embeddable-Impls-camelCase[`++SafeAllowanceCamelImpl++`] +-- + +[#ERC20-constructor-section] +==== Constructor + +[.contract-item] +[[ERC20-constructor]] +==== `[.contract-item-name]#++constructor++#++(ref self: ContractState, name: felt252, symbol: felt252, fixed_supply: u256, recipient: ContractAddress)++` [.item-kind]#constructor# + +Sets the `name` and `symbol`. +Mints `fixed_supply` tokens to `recipient`. diff --git a/docs/modules/ROOT/pages/guides/erc20-supply.adoc b/docs/modules/ROOT/pages/guides/erc20-supply.adoc index a51a2a0d2..b610bee20 100644 --- a/docs/modules/ROOT/pages/guides/erc20-supply.adoc +++ b/docs/modules/ROOT/pages/guides/erc20-supply.adoc @@ -42,20 +42,20 @@ mod MyToken { #[constructor] fn constructor( ref self: ContractState, - initial_supply: u256, + fixed_supply: u256, recipient: ContractAddress ) { let name = 'MyToken'; let symbol = 'MTK'; self.erc20.initializer(name, symbol); - self.erc20._mint(recipient, initial_supply); + self.erc20._mint(recipient, fixed_supply); } } ---- In the constructor, we're first calling the ERC20 initializer to set the token name and symbol. -Next, we're calling the internal `_mint` function which creates `initial_supply` of tokens and allocates them to `recipient`. +Next, we're calling the internal `_mint` function which creates `fixed_supply` of tokens and allocates them to `recipient`. Since the internal `_mint` is not exposed in our contract, it will not be possible to create any more tokens. In other words, we've implemented a fixed token supply! diff --git a/src/presets.cairo b/src/presets.cairo index ba3526921..a167d8e1c 100644 --- a/src/presets.cairo +++ b/src/presets.cairo @@ -1,3 +1,5 @@ mod account; +mod erc20; use account::Account; +use erc20::ERC20; diff --git a/src/presets/erc20.cairo b/src/presets/erc20.cairo new file mode 100644 index 000000000..b1c33a46f --- /dev/null +++ b/src/presets/erc20.cairo @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts for Cairo v0.8.0-beta.0 (presets/erc20.cairo) + +/// # ERC20 Preset +/// +/// The ERC20 contract offers basic functionality and provides a +/// fixed-supply mechanism for token distribution. The fixed supply is +/// set in the constructor. +#[starknet::contract] +mod ERC20 { + use openzeppelin::token::erc20::ERC20Component; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + #[abi(embed_v0)] + impl SafeAllowanceImpl = ERC20Component::SafeAllowanceImpl; + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; + #[abi(embed_v0)] + impl SafeAllowanceCamelImpl = + ERC20Component::SafeAllowanceCamelImpl; + impl InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event + } + + /// Sets the token `name` and `symbol`. + /// Mints `fixed_supply` tokens to `recipient`. + #[constructor] + fn constructor( + ref self: ContractState, + name: felt252, + symbol: felt252, + fixed_supply: u256, + recipient: ContractAddress + ) { + self.erc20.initializer(name, symbol); + self.erc20._mint(recipient, fixed_supply); + } +} diff --git a/src/tests/presets.cairo b/src/tests/presets.cairo index 2c61e9dca..9121ed966 100644 --- a/src/tests/presets.cairo +++ b/src/tests/presets.cairo @@ -1 +1,2 @@ mod test_account; +mod test_erc20; diff --git a/src/tests/presets/test_erc20.cairo b/src/tests/presets/test_erc20.cairo new file mode 100644 index 000000000..aa8174cc1 --- /dev/null +++ b/src/tests/presets/test_erc20.cairo @@ -0,0 +1,489 @@ +use integer::BoundedInt; +use openzeppelin::presets::ERC20; +use openzeppelin::tests::utils::constants::{ + ZERO, OWNER, SPENDER, RECIPIENT, NAME, SYMBOL, DECIMALS, SUPPLY, VALUE +}; +use openzeppelin::tests::utils; +use openzeppelin::token::erc20::ERC20Component::{Approval, Transfer}; +use openzeppelin::token::erc20::ERC20Component::{ERC20CamelOnlyImpl, ERC20Impl}; +use openzeppelin::token::erc20::ERC20Component::{ERC20MetadataImpl, InternalImpl}; +use openzeppelin::token::erc20::ERC20Component::{SafeAllowanceImpl, SafeAllowanceCamelImpl}; +use openzeppelin::token::erc20::interface::ERC20ABI; +use openzeppelin::token::erc20::interface::{ERC20ABIDispatcher, ERC20ABIDispatcherTrait}; +use openzeppelin::utils::serde::SerializedAppend; +use starknet::ContractAddress; +use starknet::testing; + +// +// Setup +// + +fn setup_dispatcher_with_event() -> ERC20ABIDispatcher { + let mut calldata = array![]; + + calldata.append_serde(NAME); + calldata.append_serde(SYMBOL); + calldata.append_serde(SUPPLY); + calldata.append_serde(OWNER()); + + let address = utils::deploy(ERC20::TEST_CLASS_HASH, calldata); + ERC20ABIDispatcher { contract_address: address } +} + +fn setup_dispatcher() -> ERC20ABIDispatcher { + let dispatcher = setup_dispatcher_with_event(); + utils::drop_event(dispatcher.contract_address); + dispatcher +} + +// +// constructor +// + +#[test] +#[available_gas(2000000)] +fn test_constructor() { + let mut dispatcher = setup_dispatcher_with_event(); + + assert(dispatcher.name() == NAME, 'Should be NAME'); + assert(dispatcher.symbol() == SYMBOL, 'Should be SYMBOL'); + assert(dispatcher.decimals() == DECIMALS, 'Should be DECIMALS'); + assert(dispatcher.total_supply() == SUPPLY, 'Should equal SUPPLY'); + assert(dispatcher.balance_of(OWNER()) == SUPPLY, 'Should equal SUPPLY'); + assert_only_event_transfer(dispatcher.contract_address, ZERO(), OWNER(), SUPPLY); +} + +// +// Getters +// + +#[test] +#[available_gas(2000000)] +fn test_total_supply() { + let mut dispatcher = setup_dispatcher(); + + assert(dispatcher.total_supply() == SUPPLY, 'Should equal SUPPLY'); + assert(dispatcher.totalSupply() == SUPPLY, 'Should equal SUPPLY'); +} + +#[test] +#[available_gas(2000000)] +fn test_balance_of() { + let mut dispatcher = setup_dispatcher(); + + assert(dispatcher.balance_of(OWNER()) == SUPPLY, 'Should equal SUPPLY'); + assert(dispatcher.balanceOf(OWNER()) == SUPPLY, 'Should equal SUPPLY'); +} + +#[test] +#[available_gas(2000000)] +fn test_allowance() { + let mut dispatcher = setup_dispatcher(); + + testing::set_contract_address(OWNER()); + dispatcher.approve(SPENDER(), VALUE); + assert(dispatcher.allowance(OWNER(), SPENDER()) == VALUE, 'Should equal VALUE'); +} + +// +// approve +// + +#[test] +#[available_gas(2000000)] +fn test_approve() { + let mut dispatcher = setup_dispatcher(); + assert(dispatcher.allowance(OWNER(), SPENDER()) == 0, 'Should equal ZERO'); + + testing::set_contract_address(OWNER()); + assert(dispatcher.approve(SPENDER(), VALUE), 'Should return true'); + + assert(dispatcher.allowance(OWNER(), SPENDER()) == VALUE, 'Should equal VALUE'); + assert_only_event_approval(dispatcher.contract_address, OWNER(), SPENDER(), VALUE); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('ERC20: approve from 0', 'ENTRYPOINT_FAILED'))] +fn test_approve_from_zero() { + let mut dispatcher = setup_dispatcher(); + dispatcher.approve(SPENDER(), VALUE); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] +fn test_approve_to_zero() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.approve(Zeroable::zero(), VALUE); +} + +// +// transfer +// + +#[test] +#[available_gas(2000000)] +fn test_transfer() { + let mut dispatcher = setup_dispatcher(); + + testing::set_contract_address(OWNER()); + assert(dispatcher.transfer(RECIPIENT(), VALUE), 'Should return true'); + + assert(dispatcher.balance_of(OWNER()) == SUPPLY - VALUE, 'Should equal SUPPLY - VALUE'); + assert(dispatcher.balance_of(RECIPIENT()) == VALUE, 'Should equal VALUE'); + assert(dispatcher.total_supply() == SUPPLY, 'Should equal SUPPLY'); + assert_only_event_transfer(dispatcher.contract_address, OWNER(), RECIPIENT(), VALUE); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('u256_sub Overflow', 'ENTRYPOINT_FAILED'))] +fn test_transfer_not_enough_balance() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + + let balance_plus_one = SUPPLY + 1; + dispatcher.transfer(RECIPIENT(), balance_plus_one); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('ERC20: transfer from 0', 'ENTRYPOINT_FAILED'))] +fn test_transfer_from_zero() { + let mut dispatcher = setup_dispatcher(); + dispatcher.transfer(RECIPIENT(), VALUE); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('ERC20: transfer to 0', 'ENTRYPOINT_FAILED'))] +fn test_transfer_to_zero() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.transfer(ZERO(), VALUE); +} + +// +// transfer_from & transferFrom +// + +#[test] +#[available_gas(2000000)] +fn test_transfer_from() { + let mut dispatcher = setup_dispatcher(); + + testing::set_contract_address(OWNER()); + dispatcher.approve(SPENDER(), VALUE); + utils::drop_event(dispatcher.contract_address); + + testing::set_contract_address(SPENDER()); + assert(dispatcher.transfer_from(OWNER(), RECIPIENT(), VALUE), 'Should return true'); + + assert_event_approval(dispatcher.contract_address, OWNER(), SPENDER(), 0); + assert_only_event_transfer(dispatcher.contract_address, OWNER(), RECIPIENT(), VALUE); + + assert(dispatcher.balance_of(RECIPIENT()) == VALUE, 'Should equal amount'); + assert(dispatcher.balance_of(OWNER()) == SUPPLY - VALUE, 'Should equal supply - amount'); + assert(dispatcher.allowance(OWNER(), SPENDER()) == 0, 'Should equal 0'); + assert(dispatcher.total_supply() == SUPPLY, 'Total supply should not change'); +} + +#[test] +#[available_gas(2000000)] +fn test_transfer_from_doesnt_consume_infinite_allowance() { + let mut dispatcher = setup_dispatcher(); + + testing::set_contract_address(OWNER()); + dispatcher.approve(SPENDER(), BoundedInt::max()); + + testing::set_contract_address(SPENDER()); + dispatcher.transfer_from(OWNER(), RECIPIENT(), VALUE); + + assert( + dispatcher.allowance(OWNER(), SPENDER()) == BoundedInt::max(), 'Allowance should not change' + ); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('u256_sub Overflow', 'ENTRYPOINT_FAILED'))] +fn test_transfer_from_greater_than_allowance() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.approve(SPENDER(), VALUE); + + testing::set_contract_address(SPENDER()); + let allowance_plus_one = VALUE + 1; + dispatcher.transfer_from(OWNER(), RECIPIENT(), allowance_plus_one); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('ERC20: transfer to 0', 'ENTRYPOINT_FAILED'))] +fn test_transfer_from_to_zero_address() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.approve(SPENDER(), VALUE); + + testing::set_contract_address(SPENDER()); + dispatcher.transfer_from(OWNER(), Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('u256_sub Overflow', 'ENTRYPOINT_FAILED'))] +fn test_transfer_from_from_zero_address() { + let mut dispatcher = setup_dispatcher(); + dispatcher.transfer_from(Zeroable::zero(), RECIPIENT(), VALUE); +} + +#[test] +#[available_gas(2000000)] +fn test_transferFrom() { + let mut dispatcher = setup_dispatcher(); + + testing::set_contract_address(OWNER()); + dispatcher.approve(SPENDER(), VALUE); + utils::drop_event(dispatcher.contract_address); + + testing::set_contract_address(SPENDER()); + assert(dispatcher.transferFrom(OWNER(), RECIPIENT(), VALUE), 'Should return true'); + + assert_event_approval(dispatcher.contract_address, OWNER(), SPENDER(), 0); + assert_only_event_transfer(dispatcher.contract_address, OWNER(), RECIPIENT(), VALUE); + + assert(dispatcher.balance_of(RECIPIENT()) == VALUE, 'Should equal amount'); + assert(dispatcher.balance_of(OWNER()) == SUPPLY - VALUE, 'Should equal supply - amount'); + assert(dispatcher.allowance(OWNER(), SPENDER()) == 0, 'Should equal 0'); + assert(dispatcher.total_supply() == SUPPLY, 'Total supply should not change'); +} + +#[test] +#[available_gas(2000000)] +fn test_transferFrom_doesnt_consume_infinite_allowance() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.approve(SPENDER(), BoundedInt::max()); + + testing::set_contract_address(SPENDER()); + dispatcher.transferFrom(OWNER(), RECIPIENT(), VALUE); + + assert( + dispatcher.allowance(OWNER(), SPENDER()) == BoundedInt::max(), 'Allowance should not change' + ); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('u256_sub Overflow', 'ENTRYPOINT_FAILED'))] +fn test_transferFrom_greater_than_allowance() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.approve(SPENDER(), VALUE); + + testing::set_contract_address(SPENDER()); + let allowance_plus_one = VALUE + 1; + dispatcher.transferFrom(OWNER(), RECIPIENT(), allowance_plus_one); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('ERC20: transfer to 0', 'ENTRYPOINT_FAILED'))] +fn test_transferFrom_to_zero_address() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.approve(SPENDER(), VALUE); + + testing::set_contract_address(SPENDER()); + dispatcher.transferFrom(OWNER(), Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('u256_sub Overflow', 'ENTRYPOINT_FAILED'))] +fn test_transferFrom_from_zero_address() { + let mut dispatcher = setup_dispatcher(); + dispatcher.transferFrom(Zeroable::zero(), RECIPIENT(), VALUE); +} + +// +// increase_allowance & increaseAllowance +// + +#[test] +#[available_gas(2000000)] +fn test_increase_allowance() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.approve(SPENDER(), VALUE); + utils::drop_event(dispatcher.contract_address); + + assert(dispatcher.increase_allowance(SPENDER(), VALUE), 'Should return true'); + + assert_only_event_approval(dispatcher.contract_address, OWNER(), SPENDER(), VALUE * 2); + assert(dispatcher.allowance(OWNER(), SPENDER()) == VALUE * 2, 'Should be amount * 2'); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] +fn test_increase_allowance_to_zero_address() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.increase_allowance(Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('ERC20: approve from 0', 'ENTRYPOINT_FAILED'))] +fn test_increase_allowance_from_zero_address() { + let mut dispatcher = setup_dispatcher(); + dispatcher.increase_allowance(SPENDER(), VALUE); +} + +#[test] +#[available_gas(2000000)] +fn test_increaseAllowance() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.approve(SPENDER(), VALUE); + utils::drop_event(dispatcher.contract_address); + + assert(dispatcher.increaseAllowance(SPENDER(), VALUE), 'Should return true'); + + assert_only_event_approval(dispatcher.contract_address, OWNER(), SPENDER(), 2 * VALUE); + assert(dispatcher.allowance(OWNER(), SPENDER()) == VALUE * 2, 'Should be amount * 2'); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] +fn test_increaseAllowance_to_zero_address() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.increaseAllowance(Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('ERC20: approve from 0', 'ENTRYPOINT_FAILED'))] +fn test_increaseAllowance_from_zero_address() { + let mut dispatcher = setup_dispatcher(); + dispatcher.increaseAllowance(SPENDER(), VALUE); +} + +// +// decrease_allowance & decreaseAllowance +// + +#[test] +#[available_gas(2000000)] +fn test_decrease_allowance() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.approve(SPENDER(), VALUE); + utils::drop_event(dispatcher.contract_address); + + assert(dispatcher.decrease_allowance(SPENDER(), VALUE), 'Should return true'); + + assert_only_event_approval(dispatcher.contract_address, OWNER(), SPENDER(), 0); + assert(dispatcher.allowance(OWNER(), SPENDER()) == VALUE - VALUE, 'Should be 0'); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('u256_sub Overflow', 'ENTRYPOINT_FAILED'))] +fn test_decrease_allowance_to_zero_address() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.decrease_allowance(Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('u256_sub Overflow', 'ENTRYPOINT_FAILED'))] +fn test_decrease_allowance_from_zero_address() { + let mut dispatcher = setup_dispatcher(); + dispatcher.decrease_allowance(SPENDER(), VALUE); +} + +#[test] +#[available_gas(2000000)] +fn test_decreaseAllowance() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.approve(SPENDER(), VALUE); + utils::drop_event(dispatcher.contract_address); + + assert(dispatcher.decreaseAllowance(SPENDER(), VALUE), 'Should return true'); + + assert_only_event_approval(dispatcher.contract_address, OWNER(), SPENDER(), 0); + assert(dispatcher.allowance(OWNER(), SPENDER()) == VALUE - VALUE, 'Should be 0'); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('u256_sub Overflow', 'ENTRYPOINT_FAILED'))] +fn test_decreaseAllowance_to_zero_address() { + let mut dispatcher = setup_dispatcher(); + testing::set_contract_address(OWNER()); + dispatcher.decreaseAllowance(Zeroable::zero(), VALUE); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('u256_sub Overflow', 'ENTRYPOINT_FAILED'))] +fn test_decreaseAllowance_from_zero_address() { + let mut dispatcher = setup_dispatcher(); + dispatcher.decreaseAllowance(SPENDER(), VALUE); +} + +// +// Helpers +// + +fn assert_event_approval( + contract: ContractAddress, owner: ContractAddress, spender: ContractAddress, value: u256 +) { + let event = utils::pop_log::(contract).unwrap(); + assert(event.owner == owner, 'Invalid `owner`'); + assert(event.spender == spender, 'Invalid `spender`'); + assert(event.value == value, 'Invalid `value`'); + + // Check indexed keys + let mut indexed_keys = array![]; + indexed_keys.append_serde(owner); + indexed_keys.append_serde(spender); + utils::assert_indexed_keys(event, indexed_keys.span()) +} + +fn assert_only_event_approval( + contract: ContractAddress, owner: ContractAddress, spender: ContractAddress, value: u256 +) { + assert_event_approval(contract, owner, spender, value); + utils::assert_no_events_left(contract); +} + +fn assert_event_transfer( + contract: ContractAddress, from: ContractAddress, to: ContractAddress, value: u256 +) { + let event = utils::pop_log::(contract).unwrap(); + assert(event.from == from, 'Invalid `from`'); + assert(event.to == to, 'Invalid `to`'); + assert(event.value == value, 'Invalid `value`'); + + // Check indexed keys + let mut indexed_keys = array![]; + indexed_keys.append_serde(from); + indexed_keys.append_serde(to); + utils::assert_indexed_keys(event, indexed_keys.span()); +} + +fn assert_only_event_transfer( + contract: ContractAddress, from: ContractAddress, to: ContractAddress, value: u256 +) { + assert_event_transfer(contract, from, to, value); + utils::assert_no_events_left(contract); +} diff --git a/src/tests/token/test_erc20.cairo b/src/tests/token/test_erc20.cairo index ad92ed6ae..be0eb72db 100644 --- a/src/tests/token/test_erc20.cairo +++ b/src/tests/token/test_erc20.cairo @@ -38,10 +38,10 @@ fn test_initializer() { let mut state = STATE(); state.erc20.initializer(NAME, SYMBOL); - assert(state.erc20.name() == NAME, 'Name should be NAME'); - assert(state.erc20.symbol() == SYMBOL, 'Symbol should be SYMBOL'); - assert(state.erc20.decimals() == DECIMALS, 'Decimals should be 18'); - assert(state.erc20.total_supply() == 0, 'Supply should eq 0'); + assert(state.erc20.name() == NAME, 'Should be NAME'); + assert(state.erc20.symbol() == SYMBOL, 'Should be SYMBOL'); + assert(state.erc20.decimals() == DECIMALS, 'Should be DECIMALS'); + assert(state.erc20.total_supply() == 0, 'Should equal 0'); } // @@ -53,7 +53,7 @@ fn test_initializer() { fn test_total_supply() { let mut state = STATE(); state.erc20._mint(OWNER(), SUPPLY); - assert(state.erc20.total_supply() == SUPPLY, 'Should eq SUPPLY'); + assert(state.erc20.total_supply() == SUPPLY, 'Should equal SUPPLY'); } #[test] @@ -61,7 +61,7 @@ fn test_total_supply() { fn test_totalSupply() { let mut state = STATE(); state.erc20._mint(OWNER(), SUPPLY); - assert(state.erc20.totalSupply() == SUPPLY, 'Should eq SUPPLY'); + assert(state.erc20.totalSupply() == SUPPLY, 'Should equal SUPPLY'); } #[test] @@ -69,7 +69,7 @@ fn test_totalSupply() { fn test_balance_of() { let mut state = STATE(); state.erc20._mint(OWNER(), SUPPLY); - assert(state.erc20.balance_of(OWNER()) == SUPPLY, 'Should eq SUPPLY'); + assert(state.erc20.balance_of(OWNER()) == SUPPLY, 'Should equal SUPPLY'); } #[test] @@ -77,7 +77,7 @@ fn test_balance_of() { fn test_balanceOf() { let mut state = STATE(); state.erc20._mint(OWNER(), SUPPLY); - assert(state.erc20.balanceOf(OWNER()) == SUPPLY, 'Should eq SUPPLY'); + assert(state.erc20.balanceOf(OWNER()) == SUPPLY, 'Should equal SUPPLY'); } #[test] @@ -87,7 +87,7 @@ fn test_allowance() { testing::set_caller_address(OWNER()); state.erc20.approve(SPENDER(), VALUE); - assert(state.erc20.allowance(OWNER(), SPENDER()) == VALUE, 'Should eq VALUE'); + assert(state.erc20.allowance(OWNER(), SPENDER()) == VALUE, 'Should equal VALUE'); } // @@ -119,7 +119,7 @@ fn test_approve_from_zero() { fn test_approve_to_zero() { let mut state = setup(); testing::set_caller_address(OWNER()); - state.erc20.approve(Zeroable::zero(), VALUE); + state.erc20.approve(ZERO(), VALUE); } #[test] @@ -138,7 +138,7 @@ fn test__approve() { #[should_panic(expected: ('ERC20: approve from 0',))] fn test__approve_from_zero() { let mut state = setup(); - state.erc20._approve(Zeroable::zero(), SPENDER(), VALUE); + state.erc20._approve(ZERO(), SPENDER(), VALUE); } #[test] @@ -147,7 +147,7 @@ fn test__approve_from_zero() { fn test__approve_to_zero() { let mut state = setup(); testing::set_caller_address(OWNER()); - state.erc20._approve(OWNER(), Zeroable::zero(), VALUE); + state.erc20._approve(OWNER(), ZERO(), VALUE); } // @@ -162,11 +162,39 @@ fn test_transfer() { assert(state.erc20.transfer(RECIPIENT(), VALUE), 'Should return true'); assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); - assert(state.erc20.balance_of(RECIPIENT()) == VALUE, 'Balance should eq VALUE'); - assert(state.erc20.balance_of(OWNER()) == SUPPLY - VALUE, 'Should eq supply - VALUE'); + assert(state.erc20.balance_of(RECIPIENT()) == VALUE, 'Should equal VALUE'); + assert(state.erc20.balance_of(OWNER()) == SUPPLY - VALUE, 'Should equal SUPPLY - VALUE'); assert(state.erc20.total_supply() == SUPPLY, 'Total supply should not change'); } +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('u256_sub Overflow',))] +fn test_transfer_not_enough_balance() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + + let balance_plus_one = SUPPLY + 1; + state.erc20.transfer(RECIPIENT(), balance_plus_one); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('ERC20: transfer from 0',))] +fn test_transfer_from_zero() { + let mut state = setup(); + state.erc20.transfer(RECIPIENT(), VALUE); +} + +#[test] +#[available_gas(2000000)] +#[should_panic(expected: ('ERC20: transfer to 0',))] +fn test_transfer_to_zero() { + let mut state = setup(); + testing::set_caller_address(OWNER()); + state.erc20.transfer(ZERO(), VALUE); +} + #[test] #[available_gas(2000000)] fn test__transfer() { @@ -175,8 +203,8 @@ fn test__transfer() { state.erc20._transfer(OWNER(), RECIPIENT(), VALUE); assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); - assert(state.erc20.balance_of(RECIPIENT()) == VALUE, 'Balance should eq amount'); - assert(state.erc20.balance_of(OWNER()) == SUPPLY - VALUE, 'Should eq supply - amount'); + assert(state.erc20.balance_of(RECIPIENT()) == VALUE, 'Should equal amount'); + assert(state.erc20.balance_of(OWNER()) == SUPPLY - VALUE, 'Should equal SUPPLY - VALUE'); assert(state.erc20.total_supply() == SUPPLY, 'Total supply should not change'); } @@ -196,7 +224,7 @@ fn test__transfer_not_enough_balance() { #[should_panic(expected: ('ERC20: transfer from 0',))] fn test__transfer_from_zero() { let mut state = setup(); - state.erc20._transfer(Zeroable::zero(), RECIPIENT(), VALUE); + state.erc20._transfer(ZERO(), RECIPIENT(), VALUE); } #[test] @@ -204,7 +232,7 @@ fn test__transfer_from_zero() { #[should_panic(expected: ('ERC20: transfer to 0',))] fn test__transfer_to_zero() { let mut state = setup(); - state.erc20._transfer(OWNER(), Zeroable::zero(), VALUE); + state.erc20._transfer(OWNER(), ZERO(), VALUE); } // @@ -225,9 +253,9 @@ fn test_transfer_from() { assert_event_approval(OWNER(), SPENDER(), 0); assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); - assert(state.erc20.balance_of(RECIPIENT()) == VALUE, 'Should eq amount'); - assert(state.erc20.balance_of(OWNER()) == SUPPLY - VALUE, 'Should eq supply - amount'); - assert(state.erc20.allowance(OWNER(), SPENDER()) == 0, 'Should eq 0'); + assert(state.erc20.balance_of(RECIPIENT()) == VALUE, 'Should equal VALUE'); + assert(state.erc20.balance_of(OWNER()) == SUPPLY - VALUE, 'Should equal SUPPLY - VALUE'); + assert(state.erc20.allowance(OWNER(), SPENDER()) == 0, 'Should equal 0'); assert(state.erc20.total_supply() == SUPPLY, 'Total supply should not change'); } @@ -269,7 +297,7 @@ fn test_transfer_from_to_zero_address() { state.erc20.approve(SPENDER(), VALUE); testing::set_caller_address(SPENDER()); - state.erc20.transfer_from(OWNER(), Zeroable::zero(), VALUE); + state.erc20.transfer_from(OWNER(), ZERO(), VALUE); } #[test] @@ -277,7 +305,7 @@ fn test_transfer_from_to_zero_address() { #[should_panic(expected: ('u256_sub Overflow',))] fn test_transfer_from_from_zero_address() { let mut state = setup(); - state.erc20.transfer_from(Zeroable::zero(), RECIPIENT(), VALUE); + state.erc20.transfer_from(ZERO(), RECIPIENT(), VALUE); } #[test] @@ -294,9 +322,9 @@ fn test_transferFrom() { assert_event_approval(OWNER(), SPENDER(), 0); assert_only_event_transfer(OWNER(), RECIPIENT(), VALUE); - assert(state.erc20.balanceOf(RECIPIENT()) == VALUE, 'Should eq amount'); - assert(state.erc20.balanceOf(OWNER()) == SUPPLY - VALUE, 'Should eq supply - amount'); - assert(state.erc20.allowance(OWNER(), SPENDER()) == 0, 'Should eq 0'); + assert(state.erc20.balanceOf(RECIPIENT()) == VALUE, 'Should equal VALUE'); + assert(state.erc20.balanceOf(OWNER()) == SUPPLY - VALUE, 'Should equal SUPPLY - VALUE'); + assert(state.erc20.allowance(OWNER(), SPENDER()) == 0, 'Should equal 0'); assert(state.erc20.totalSupply() == SUPPLY, 'Total supply should not change'); } @@ -338,7 +366,7 @@ fn test_transferFrom_to_zero_address() { state.erc20.approve(SPENDER(), VALUE); testing::set_caller_address(SPENDER()); - state.erc20.transferFrom(OWNER(), Zeroable::zero(), VALUE); + state.erc20.transferFrom(OWNER(), ZERO(), VALUE); } #[test] @@ -346,7 +374,7 @@ fn test_transferFrom_to_zero_address() { #[should_panic(expected: ('u256_sub Overflow',))] fn test_transferFrom_from_zero_address() { let mut state = setup(); - state.erc20.transferFrom(Zeroable::zero(), RECIPIENT(), VALUE); + state.erc20.transferFrom(ZERO(), RECIPIENT(), VALUE); } // @@ -364,7 +392,7 @@ fn test_increase_allowance() { assert(state.erc20.increase_allowance(SPENDER(), VALUE), 'Should return true'); assert_only_event_approval(OWNER(), SPENDER(), VALUE * 2); - assert(state.erc20.allowance(OWNER(), SPENDER()) == VALUE * 2, 'Should be amount * 2'); + assert(state.erc20.allowance(OWNER(), SPENDER()) == VALUE * 2, 'Should equal VALUE * 2'); } #[test] @@ -373,7 +401,7 @@ fn test_increase_allowance() { fn test_increase_allowance_to_zero_address() { let mut state = setup(); testing::set_caller_address(OWNER()); - state.erc20.increase_allowance(Zeroable::zero(), VALUE); + state.erc20.increase_allowance(ZERO(), VALUE); } #[test] @@ -395,7 +423,7 @@ fn test_increaseAllowance() { assert(state.erc20.increaseAllowance(SPENDER(), VALUE), 'Should return true'); assert_only_event_approval(OWNER(), SPENDER(), 2 * VALUE); - assert(state.erc20.allowance(OWNER(), SPENDER()) == VALUE * 2, 'Should be amount * 2'); + assert(state.erc20.allowance(OWNER(), SPENDER()) == VALUE * 2, 'Should equal VALUE * 2'); } #[test] @@ -404,7 +432,7 @@ fn test_increaseAllowance() { fn test_increaseAllowance_to_zero_address() { let mut state = setup(); testing::set_caller_address(OWNER()); - state.erc20.increaseAllowance(Zeroable::zero(), VALUE); + state.erc20.increaseAllowance(ZERO(), VALUE); } #[test] @@ -439,7 +467,7 @@ fn test_decrease_allowance() { fn test_decrease_allowance_to_zero_address() { let mut state = setup(); testing::set_caller_address(OWNER()); - state.erc20.decrease_allowance(Zeroable::zero(), VALUE); + state.erc20.decrease_allowance(ZERO(), VALUE); } #[test] @@ -470,7 +498,7 @@ fn test_decreaseAllowance() { fn test_decreaseAllowance_to_zero_address() { let mut state = setup(); testing::set_caller_address(OWNER()); - state.erc20.decreaseAllowance(Zeroable::zero(), VALUE); + state.erc20.decreaseAllowance(ZERO(), VALUE); } #[test] @@ -497,7 +525,7 @@ fn test__spend_allowance_not_unlimited() { assert_only_event_approval(OWNER(), SPENDER(), SUPPLY - VALUE); assert( - state.erc20.allowance(OWNER(), SPENDER()) == SUPPLY - VALUE, 'Should eq supply - amount' + state.erc20.allowance(OWNER(), SPENDER()) == SUPPLY - VALUE, 'Should equal SUPPLY - VALUE' ); } @@ -527,8 +555,8 @@ fn test__mint() { state.erc20._mint(OWNER(), VALUE); assert_only_event_transfer(ZERO(), OWNER(), VALUE); - assert(state.erc20.balance_of(OWNER()) == VALUE, 'Should eq amount'); - assert(state.erc20.total_supply() == VALUE, 'Should eq total supply'); + assert(state.erc20.balance_of(OWNER()) == VALUE, 'Should equal VALUE'); + assert(state.erc20.total_supply() == VALUE, 'Should equal VALUE'); } #[test] @@ -536,7 +564,7 @@ fn test__mint() { #[should_panic(expected: ('ERC20: mint to 0',))] fn test__mint_to_zero() { let mut state = STATE(); - state.erc20._mint(Zeroable::zero(), VALUE); + state.erc20._mint(ZERO(), VALUE); } // @@ -550,8 +578,8 @@ fn test__burn() { state.erc20._burn(OWNER(), VALUE); assert_only_event_transfer(OWNER(), ZERO(), VALUE); - assert(state.erc20.total_supply() == SUPPLY - VALUE, 'Should eq supply - amount'); - assert(state.erc20.balance_of(OWNER()) == SUPPLY - VALUE, 'Should eq supply - amount'); + assert(state.erc20.total_supply() == SUPPLY - VALUE, 'Should equal SUPPLY - VALUE'); + assert(state.erc20.balance_of(OWNER()) == SUPPLY - VALUE, 'Should equal SUPPLY - VALUE'); } #[test] @@ -559,7 +587,7 @@ fn test__burn() { #[should_panic(expected: ('ERC20: burn from 0',))] fn test__burn_from_zero() { let mut state = setup(); - state.erc20._burn(Zeroable::zero(), VALUE); + state.erc20._burn(ZERO(), VALUE); } //