From 421888b5cd74feaa53b10a50eb95b33899ad45ed Mon Sep 17 00:00:00 2001 From: Lawal Abubakar Babatunde Date: Tue, 9 Jan 2024 12:04:51 +0100 Subject: [PATCH 1/4] Chore: initial repo --- data/constructor_args.txt | 1 + src/bwc_erc20_token.cairo | 153 ++++++++++++++++++++++++++++++++++---- 2 files changed, 138 insertions(+), 16 deletions(-) create mode 100644 data/constructor_args.txt diff --git a/data/constructor_args.txt b/data/constructor_args.txt new file mode 100644 index 0000000..181a15c --- /dev/null +++ b/data/constructor_args.txt @@ -0,0 +1 @@ +'admin' \ No newline at end of file diff --git a/src/bwc_erc20_token.cairo b/src/bwc_erc20_token.cairo index 17ef726..64bf31a 100644 --- a/src/bwc_erc20_token.cairo +++ b/src/bwc_erc20_token.cairo @@ -65,27 +65,23 @@ mod BWCERC20Token { // Constructor #[constructor] - fn constructor( - ref self: ContractState, - _name: felt252, - _symbol: felt252, - _decimal: u8, - _initial_supply: u256, - recipient: ContractAddress - ) { + fn constructor(ref self: ContractState, // _name: felt252, + // _symbol: felt252, + // _decimal: u8, + // _initial_supply: u256, + recipient: ContractAddress) { // 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.name.write('BlockheaderToken'); + self.symbol.write('BHT'); + self.decimals.write(18); + self.total_supply.write(1000000); + self.balances.write(recipient, 1000000); 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 + Transfer { //Here, `contract_address_const::<0>()` is similar to address(0) in Solidity + from: contract_address_const::<0>(), to: recipient, value: 1000000 } ); } @@ -218,3 +214,128 @@ mod BWCERC20Token { } } } + + +#[cfg(test)] +mod test { + use core::serde::Serde; + use super::{IERC20, BWCERC20Token, IERC20Dispatcher, IERC20DispatcherTrait}; + use starknet::ContractAddress; + use starknet::contract_address::contract_address_const; + use array::ArrayTrait; + use snforge_std::{declare, ContractClassTrait, fs::{FileTrait, read_txt}}; + use snforge_std::{start_prank, stop_prank, CheatTarget}; + use snforge_std::PrintTrait; + use traits::{Into, TryInto}; + + // helper function + fn deploy_contract() -> ContractAddress { + let erc20_contract_class = declare('BWCERC20Token'); + let file = FileTrait::new('data/constructor_args.txt'); + let constructor_args = read_txt(@file); + + let contract_address = erc20_contract_class.deploy(@constructor_args).unwrap(); + contract_address + } + + #[test] + fn test_constructor() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + let name = dispatcher.get_name(); + assert(name == 'BlockheaderToken', 'name is not correct'); + } + + #[test] + fn test_symbol_is_correct() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + let symbol = dispatcher.get_symbol(); + assert(symbol == 'BHT', 'symbol is not correct'); + } + + #[test] + fn test_decimal_is_correct() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + let decimal = dispatcher.get_decimals(); + assert(decimal == 18, Errors::INVALID_DECIMALS); + } + + #[test] + fn test_total_supply() { + let address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address: address }; + let total_supply = dispatcher.get_total_supply(); + assert(total_supply == 1000000, Errors::UNMATCHED_SUPPLY); + } + + #[test] + fn test_address_balance() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + let balance = dispatcher.get_total_supply(); + let admin_balance = dispatcher.balance_of(Account::admin()); + assert(admin_balance == balance, Errors::INVALID_BALANCE); + + start_prank(CheatTarget::One(contract_address), Account::admin()); + dispatcher.transfer(Account::user1(), 10); + let new_admin_balance = dispatcher.balance_of(Account::admin()); + new_admin_balance.print(); + assert(new_admin_balance == balance - 10, Errors::INVALID_BALANCE); + stop_prank(CheatTarget::One(contract_address)); + + let user1_balance = dispatcher.balance_of(Account::user1()); + assert(user1_balance == 10, Errors::INVALID_BALANCE); + } + + #[test] + fn test_allowance() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + + start_prank(CheatTarget::One(contract_address), Account::admin()); + dispatcher.approve(contract_address, 10); + assert( + dispatcher.allowance(Account::admin(), contract_address) == 10, Errors::INVALID_BALANCE + ); + stop_prank(CheatTarget::One(contract_address)); + } + + #[test] + fn test_transfer() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + start_prank(CheatTarget::One(contract_address), Account::admin()); + dispatcher.transfer(Account::user1(), 10); + let user1_balance = dispatcher.balance_of(Account::user1()); + assert(user1_balance == 10, Errors::INVALID_BALANCE); + + stop_prank(CheatTarget::One(contract_address)); + } + + + mod Errors { + const INVALID_DECIMALS: felt252 = 'Invalid decimals'; + const UNMATCHED_SUPPLY: felt252 = 'Unmatched supply'; + const INVALID_BALANCE: felt252 = 'Invalid balance'; + } + + mod Account { + use core::option::OptionTrait; + use starknet::ContractAddress; + use traits::TryInto; + + fn user1() -> ContractAddress { + 'joy'.try_into().unwrap() + } + + fn user2() -> ContractAddress { + 'caleb'.try_into().unwrap() + } + fn admin() -> ContractAddress { + 'admin'.try_into().unwrap() + } + } +} + From 37970ba8dbbd15a1a10567948cf0054ddc2439df Mon Sep 17 00:00:00 2001 From: Lawal Abubakar Babatunde Date: Wed, 10 Jan 2024 07:36:59 +0100 Subject: [PATCH 2/4] chore: updated readme to reflect testing --- README.md | 77 +++++-------------------------------------------------- 1 file changed, 7 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 05f1caa..a9ffab4 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,14 @@ -# Introduction to Starknet Contracts +# Introduction to Testing Starknet Contract + +- Test suite, unit tests are provided under the each contract's implementations directly whereas full flow integration tests lies within this test suite. We use starknet-foundry testing framework in this class and test thoroughly for any edge cases in each of the contract. + +## Running Tests + 1. Install starknet-foundry by running this command: `curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh` restart your terminal run `snfoundryup` -2. Create an account on any of these RPC provides: - - [Voyager](https://voyager.online/) - - [BlastAPI](https://starknet-testnet.blastapi.io) - - [Infura](https://www.infura.io/) - -Generate an RPC apikey to interact with the network - -3. Create a contract account by running this command on your terminal: -`sncast -u account create -n --add-profile` - -4. Deploy the contract account: -`sncast --url account deploy --name --max-fee 4323000047553` -`NB` -Running the above command should trigger an error: -`error: Account balance is smaller than the transaction's max_fee.` -That why your account must be funded; to fund your account, visit - https://faucet.goerli.starknet.io/ - -5. Compile your contract by running: `scarb build` - -6. Declare your contract: -`sncast --account test_deploy -u declare --contract-name ` - -7. Deploy your contract: -`sncast --account --url deploy --class-hash ` - -`NB` -While deploying, make sure you check the constructor argument of the contract you are trying to deploy. All arguments must be passed in appropriately; for such case, use this command: -```sncast --account --url deploy --class-hash --constructor-calldata ``` - - - - ---- -# Introduction to Dispatchers - - -### Deployed Contracts - -#### Ownable Contract -- [x] class hash - 0x421a3ad93deda96f863e26ab51a79f4cea384d71714a5b37ace35010872a088 -- [x] address - 0x4a742edef4df3d3fb09809535a322971ababb1f337ffcf5c297a941f54a76e1 - -#### Counter Contract -- [x] class hash - 0x71d83bb407cdd1a963bdcba92c82b3ff18e8e56fd3cfa9410b0dce069477511 -- [x] address - 0x14b32ec4783dabf825bb2ff4c82b20a81273455cf90ff263c85216b54b1f36d - -#### Caller Contract -- [x] class hash - 0x6c9d24030d72669af3e857dc1f04981c5cf316e0c2efee443509bbf95530587 -- [x] address - 0x2ee3772f1ec48d45bd6280daf74bc35eacd8f5dd741daceaea04130bade808 - - - ---- -### Interacting with Deployed Contracts -- Invoke: to execute the logic of a state-changing (writes) function within your deployed contracts from the terminal, run -``` -sncast --url --account invoke --contract-address --function "" --calldata -``` - - -- Call: to execute the logic of a non-state-changing (reads) function within your deployed contracts from the terminal, run: -``` -sncast --url --account call --contract-address --function " Date: Thu, 11 Jan 2024 00:06:10 +0100 Subject: [PATCH 3/4] This fixes #17 Fixed the #17 issue in the repo and also completed the tests for the contract. --- src/bwc_erc20_token.cairo | 226 ++++++++++++++++++++++++++++++-------- 1 file changed, 181 insertions(+), 45 deletions(-) diff --git a/src/bwc_erc20_token.cairo b/src/bwc_erc20_token.cairo index 64bf31a..b449a75 100644 --- a/src/bwc_erc20_token.cairo +++ b/src/bwc_erc20_token.cairo @@ -63,7 +63,7 @@ mod BWCERC20Token { // Note: The contract constructor is not part of the interface. Nor are internal functions part of the interface. - // Constructor + // Constructor #[constructor] fn constructor(ref self: ContractState, // _name: felt252, // _symbol: felt252, @@ -72,6 +72,11 @@ mod BWCERC20Token { recipient: ContractAddress) { // 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.name.write('BlockheaderToken'); self.symbol.write('BHT'); self.decimals.write(18); @@ -126,8 +131,9 @@ mod BWCERC20Token { amount: u256 ) { let caller = get_caller_address(); - let my_allowance = self.allowances.read((sender, recipient)); - assert(my_allowance <= amount, 'Amount Not Allowed'); + let my_allowance = self.allowances.read((sender, caller)); + assert(my_allowance > 0, 'Not approved to spend!'); + assert(amount <= my_allowance, 'Amount Not Allowed'); self .spend_allowance( sender, caller, amount @@ -201,7 +207,7 @@ mod BWCERC20Token { // define a variable ONES_MASK of type u128 let ONES_MASK = 0xfffffffffffffffffffffffffffffff_u128; - // to determine whether the authorization is unlimited, + // to determine whether the authorization is unlimited, let is_unlimited_allowance = current_allowance.low == ONES_MASK && current_allowance @@ -216,6 +222,7 @@ mod BWCERC20Token { } +// Annotation #[cfg(test)] mod test { use core::serde::Serde; @@ -228,30 +235,49 @@ mod test { use snforge_std::PrintTrait; use traits::{Into, TryInto}; - // helper function + // We first have to deploy first via a helper function fn deploy_contract() -> ContractAddress { - let erc20_contract_class = declare('BWCERC20Token'); + // Before deploying a starknet contract, we need a contract_class. + // Get it using the declare function from starknetFoundry + let erc20contract_class = declare('BWCERC20Token'); + + // Supply values the constructor arguements when deploying + // REMEMBER: It has to be in an array let file = FileTrait::new('data/constructor_args.txt'); let constructor_args = read_txt(@file); - - let contract_address = erc20_contract_class.deploy(@constructor_args).unwrap(); + let contract_address = erc20contract_class.deploy(@constructor_args).unwrap(); contract_address } + // Generate an address + mod Account { + use starknet::ContractAddress; + use traits::TryInto; + + fn user1() -> ContractAddress { + 'joy'.try_into().unwrap() + } + fn user2() -> ContractAddress { + 'caleb'.try_into().unwrap() + } + + fn admin() -> ContractAddress { + 'admin'.try_into().unwrap() + } + } + + + // --------------------- Now we start testing -------------------------------- + + // Test 1 - Test wether we can get the name #[test] fn test_constructor() { let contract_address = deploy_contract(); let dispatcher = IERC20Dispatcher { contract_address }; + // let name = dispatcher.get_name(); let name = dispatcher.get_name(); - assert(name == 'BlockheaderToken', 'name is not correct'); - } - #[test] - fn test_symbol_is_correct() { - let contract_address = deploy_contract(); - let dispatcher = IERC20Dispatcher { contract_address }; - let symbol = dispatcher.get_symbol(); - assert(symbol == 'BHT', 'symbol is not correct'); + assert(name == 'BlockheaderToken', 'name is not correct'); } #[test] @@ -259,15 +285,17 @@ mod test { let contract_address = deploy_contract(); let dispatcher = IERC20Dispatcher { contract_address }; let decimal = dispatcher.get_decimals(); - assert(decimal == 18, Errors::INVALID_DECIMALS); + + assert(decimal == 18, 'Decimal is not correct'); } #[test] fn test_total_supply() { - let address = deploy_contract(); - let dispatcher = IERC20Dispatcher { contract_address: address }; + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; let total_supply = dispatcher.get_total_supply(); - assert(total_supply == 1000000, Errors::UNMATCHED_SUPPLY); + + assert(total_supply == 1000000, 'Total supply is wrong'); } #[test] @@ -279,9 +307,9 @@ mod test { assert(admin_balance == balance, Errors::INVALID_BALANCE); start_prank(CheatTarget::One(contract_address), Account::admin()); + dispatcher.transfer(Account::user1(), 10); let new_admin_balance = dispatcher.balance_of(Account::admin()); - new_admin_balance.print(); assert(new_admin_balance == balance - 10, Errors::INVALID_BALANCE); stop_prank(CheatTarget::One(contract_address)); @@ -295,47 +323,155 @@ mod test { let dispatcher = IERC20Dispatcher { contract_address }; start_prank(CheatTarget::One(contract_address), Account::admin()); - dispatcher.approve(contract_address, 10); + dispatcher.approve(contract_address, 20); + + let currentAllowance = dispatcher.allowance(Account::admin(), contract_address); + + assert(currentAllowance == 20, Errors::INVALID_ALLOWANCE_GIVEN); + stop_prank(CheatTarget::One(contract_address)); + } + + #[test] + fn test_transfer() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + + // Get original balances + let original_sender_balance = dispatcher.balance_of(Account::admin()); + let original_recipient_balance = dispatcher.balance_of(Account::user1()); + + start_prank(CheatTarget::One(contract_address), Account::admin()); + + dispatcher.transfer(Account::user1(), 50); + + // Confirm that the funds have been sent! assert( - dispatcher.allowance(Account::admin(), contract_address) == 10, Errors::INVALID_BALANCE + dispatcher.balance_of(Account::admin()) == original_sender_balance - 50, + Errors::FUNDS_NOT_SENT ); + + // Confirm that the funds have been recieved! + assert( + dispatcher.balance_of(Account::user1()) == original_recipient_balance + 50, + Errors::FUNDS_NOT_RECIEVED + ); + stop_prank(CheatTarget::One(contract_address)); } + #[test] - fn test_transfer() { + fn test_transfer_from() { let contract_address = deploy_contract(); let dispatcher = IERC20Dispatcher { contract_address }; + start_prank(CheatTarget::One(contract_address), Account::admin()); - dispatcher.transfer(Account::user1(), 10); - let user1_balance = dispatcher.balance_of(Account::user1()); - assert(user1_balance == 10, Errors::INVALID_BALANCE); + dispatcher.approve(Account::user1(), 20); + stop_prank(CheatTarget::One(contract_address)); + + assert( + dispatcher.allowance(Account::admin(), Account::user1()) == 20, + Errors::INVALID_ALLOWANCE_GIVEN + ); + start_prank(CheatTarget::One(contract_address), Account::user1()); + dispatcher.transfer_from(Account::admin(), Account::user2(), 10); + assert( + dispatcher.allowance(Account::admin(), Account::user1()) == 10, Errors::FUNDS_NOT_SENT + ); stop_prank(CheatTarget::One(contract_address)); } + #[test] + #[should_panic(expected: ('Not approved to spend!',))] + fn test_not_approved_to_spend_error() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + start_prank(CheatTarget::One(contract_address), Account::user1()); + dispatcher.transfer_from(Account::admin(), Account::user2(), 40); + } - mod Errors { - const INVALID_DECIMALS: felt252 = 'Invalid decimals'; - const UNMATCHED_SUPPLY: felt252 = 'Unmatched supply'; - const INVALID_BALANCE: felt252 = 'Invalid balance'; + #[test] + #[should_panic(expected: ('Amount Not Allowed',))] + fn test_should_panic_when_amount_transferred_not_allowed() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + + start_prank(CheatTarget::One(contract_address), Account::admin()); + dispatcher.approve(Account::user1(), 20); + stop_prank(CheatTarget::One(contract_address)); + + assert( + dispatcher.allowance(Account::admin(), Account::user1()) == 20, + Errors::INVALID_ALLOWANCE_GIVEN + ); + + start_prank(CheatTarget::One(contract_address), Account::user1()); + dispatcher.transfer_from(Account::admin(), Account::user2(), 80); } - mod Account { - use core::option::OptionTrait; - use starknet::ContractAddress; - use traits::TryInto; + #[test] + fn test_approve() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; - fn user1() -> ContractAddress { - 'joy'.try_into().unwrap() - } + start_prank(CheatTarget::One(contract_address), Account::admin()); + dispatcher.approve(Account::user1(), 50); + assert( + dispatcher.allowance(Account::admin(), Account::user1()) == 50, + Errors::INVALID_ALLOWANCE_GIVEN + ); + } - fn user2() -> ContractAddress { - 'caleb'.try_into().unwrap() - } - fn admin() -> ContractAddress { - 'admin'.try_into().unwrap() - } + #[test] + fn test_increase_allowance() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + + start_prank(CheatTarget::One(contract_address), Account::admin()); + dispatcher.approve(Account::user1(), 30); + assert( + dispatcher.allowance(Account::admin(), Account::user1()) == 30, + Errors::INVALID_ALLOWANCE_GIVEN + ); + + dispatcher.increase_allowance(Account::user1(), 20); + + assert( + dispatcher.allowance(Account::admin(), Account::user1()) == 50, + Errors::ERROR_INCREASING_ALLOWANCE + ); } -} + #[test] + fn test_decrease_allowance() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + + start_prank(CheatTarget::One(contract_address), Account::admin()); + dispatcher.approve(Account::user1(), 30); + assert( + dispatcher.allowance(Account::admin(), Account::user1()) == 30, + Errors::INVALID_ALLOWANCE_GIVEN + ); + + dispatcher.decrease_allowance(Account::user1(), 5); + + assert( + dispatcher.allowance(Account::admin(), Account::user1()) == 25, + Errors::ERROR_DECREASING_ALLOWANCE + ); + } + + // Custom errors for error handling + mod Errors { + const INVALID_DECIMALS: felt252 = 'Invalid decimals!'; + const UNMATCHED_SUPPLY: felt252 = 'Unmatched supply!'; + const INVALID_BALANCE: felt252 = 'Invalid balance!'; + const INVALID_ALLOWANCE_GIVEN: felt252 = 'Invalid allowance given'; + const FUNDS_NOT_SENT: felt252 = 'Funds not sent!'; + const FUNDS_NOT_RECIEVED: felt252 = 'Funds not recieved!'; + const ERROR_INCREASING_ALLOWANCE: felt252 = 'Allowance not increased'; + const ERROR_DECREASING_ALLOWANCE: felt252 = 'Allowance not decreased'; + } +} From 612d7cfb405dc2f185d42410644a7053784b625c Mon Sep 17 00:00:00 2001 From: Lawal Abubakar Babatunde Date: Thu, 11 Jan 2024 08:59:20 +0100 Subject: [PATCH 4/4] chore: tested more functions --- src/bwc_erc20_token.cairo | 47 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/src/bwc_erc20_token.cairo b/src/bwc_erc20_token.cairo index 64bf31a..4952b1e 100644 --- a/src/bwc_erc20_token.cairo +++ b/src/bwc_erc20_token.cairo @@ -126,8 +126,10 @@ mod BWCERC20Token { amount: u256 ) { let caller = get_caller_address(); - let my_allowance = self.allowances.read((sender, recipient)); - assert(my_allowance <= amount, 'Amount Not Allowed'); + let my_allowance = self.allowances.read((sender, caller)); + assert(my_allowance > 0, 'You have no token approved'); + assert(amount <= my_allowance, 'Amount Not Allowed'); + // assert(my_allowance <= amount, 'Amount Not Allowed'); self .spend_allowance( sender, caller, amount @@ -314,11 +316,52 @@ mod test { stop_prank(CheatTarget::One(contract_address)); } + #[test] + fn test_transfer_from() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + let user1 = Account::user1(); + start_prank(CheatTarget::One(contract_address), Account::admin()); + dispatcher.approve(user1, 10); + assert(dispatcher.allowance(Account::admin(), user1) == 10, Errors::NOT_ALLOWED); + stop_prank(CheatTarget::One(contract_address)); + + start_prank(CheatTarget::One(contract_address), user1); + dispatcher.transfer_from(Account::admin(), Account::user2(), 5); + assert(dispatcher.balance_of(Account::user2()) == 5, Errors::INVALID_BALANCE); + // dispatcher.transfer_from(Account::admin(), user1, 15); + // assert(dispatcher.balance_of(user1) == 5, Errors::INVALID_BALANCE); + stop_prank(CheatTarget::One(contract_address)); + } + + #[test] + #[should_panic(expected: ('Amount Not Allowed', ))] + fn test_transfer_from_should_fail() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher {contract_address}; + start_prank(CheatTarget::One(contract_address), Account::admin()); + dispatcher.approve(Account::user1(), 20); + stop_prank(CheatTarget::One(contract_address)); + + start_prank(CheatTarget::One(contract_address), Account::user1()); + dispatcher.transfer_from(Account::admin(), Account::user2(), 40); + } + + #[test] + #[should_panic(expected: ('You have no token approved', ))] + fn test_transfer_from_failed_when_not_approved() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + start_prank(CheatTarget::One(contract_address), Account::user1()); + dispatcher.transfer_from(Account::admin(), Account::user2(), 5); + } + mod Errors { const INVALID_DECIMALS: felt252 = 'Invalid decimals'; const UNMATCHED_SUPPLY: felt252 = 'Unmatched supply'; const INVALID_BALANCE: felt252 = 'Invalid balance'; + const NOT_ALLOWED: felt252 = 'Not allowed'; } mod Account {