diff --git a/.all-contributorsrc b/.all-contributorsrc index cf9c2ca0..65d88ab3 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -36,6 +36,33 @@ "contributions": [ "code" ] + }, + { + "login": "enitrat", + "name": "Mathieu", + "avatar_url": "https://avatars.githubusercontent.com/u/60658558?v=4", + "profile": "https://github.com/enitrat", + "contributions": [ + "code" + ] + }, + { + "login": "Megumiiiiii", + "name": "megumii", + "avatar_url": "https://avatars.githubusercontent.com/u/98658943?v=4", + "profile": "https://github.com/Megumiiiiii", + "contributions": [ + "code" + ] + }, + { + "login": "Pjewels", + "name": "Pjewels", + "avatar_url": "https://avatars.githubusercontent.com/u/149668320?v=4", + "profile": "https://github.com/Pjewels", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml new file mode 100644 index 00000000..babbb36d --- /dev/null +++ b/.github/workflows/scorecards.yml @@ -0,0 +1,54 @@ +name: Scorecard analysis workflow +on: + # Only the default branch is supported. + branch_protection_rule: + schedule: + # Weekly on Saturdays. + - cron: "30 1 * * 6" + push: + branches: [main] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed if using Code scanning alerts + security-events: write + # Needed for GitHub OIDC token if publish_results is true + id-token: write + + steps: + - name: "Checkout code" + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + with: + results_file: results.sarif + results_format: sarif + # Publish the results for public repositories to enable scorecard badges. For more details, see + # https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories, `publish_results` will automatically be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: SARIF file + path: results.sarif + retention-days: 30 + + # required for Code scanning alerts + - name: "Upload SARIF results to code scanning" + uses: github/codeql-action/upload-sarif@49abf0ba24d0b7953cb586944e918a0b92074c80 # v2.22.4 + with: + sarif_file: results.sarif diff --git a/README.md b/README.md index 53dd8798..5e2b1a34 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ + + + +

@@ -14,6 +18,22 @@ https://unruggable.meme

+ + + +
+
+ +GitHub Workflow Status (with event) +OpenSSF Scorecard Report +Project license +Follow Unruggable Meme on Twitter +
+ + + + + Tired of getting *rugpulled*? Introducing Unruggable Meme, a project designed with security and transparency at its core. Our innovative contracts and safeguards ensure a fair and secure experience for all users. Become a meme lord and launch your own safe memecoin with [Unruggable Meme](https://unruggable.meme)! @@ -59,6 +79,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Abdel @ StarkWare
Abdel @ StarkWare

💻 Charles Lanier
Charles Lanier

💻 Francesco Ceccon
Francesco Ceccon

💻 + Mathieu
Mathieu

💻 + megumii
megumii

💻 diff --git a/contracts/.tool-versions b/contracts/.tool-versions new file mode 100644 index 00000000..76dbb56c --- /dev/null +++ b/contracts/.tool-versions @@ -0,0 +1,2 @@ +scarb 2.3.1 +starknet-foundry 0.12.0 diff --git a/contracts/Scarb.toml b/contracts/Scarb.toml index b81026f6..f3cdbdfb 100644 --- a/contracts/Scarb.toml +++ b/contracts/Scarb.toml @@ -18,3 +18,6 @@ allowed-libfuncs-list.name = "experimental" [cairo] sierra-replace-ids = true + +[tool.fmt] +sort-module-level-items = true diff --git a/contracts/src/lib.cairo b/contracts/src/lib.cairo index ac2ae0ab..2e2d51a8 100644 --- a/contracts/src/lib.cairo +++ b/contracts/src/lib.cairo @@ -1,3 +1,4 @@ mod tokens { + mod interface; mod memecoin; } diff --git a/contracts/src/tokens/interface.cairo b/contracts/src/tokens/interface.cairo new file mode 100644 index 00000000..0b9ab808 --- /dev/null +++ b/contracts/src/tokens/interface.cairo @@ -0,0 +1,65 @@ +use openzeppelin::token::erc20::interface::{IERC20Metadata, IERC20, IERC20Camel}; +use starknet::ContractAddress; + + +#[starknet::interface] +trait IUnruggableMemecoin { + // ************************************ + // * Metadata + // ************************************ + fn name(self: @TState) -> felt252; + fn symbol(self: @TState) -> felt252; + fn decimals(self: @TState) -> u8; + + // ************************************ + // * snake_case + // ************************************ + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; + + // ************************************ + // * camelCase + // ************************************ + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + + // ************************************ + // * Additional functions + // ************************************ + fn launch_memecoin(ref self: TState); +} + +#[starknet::interface] +trait IUnruggableMemecoinCamel { + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; +} + +#[starknet::interface] +trait IUnruggableMemecoinSnake { + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} + +#[starknet::interface] +trait IUnruggableAdditional { + fn launch_memecoin(ref self: TState); +} diff --git a/contracts/src/tokens/memecoin.cairo b/contracts/src/tokens/memecoin.cairo index 9f6364a4..eb6a881e 100644 --- a/contracts/src/tokens/memecoin.cairo +++ b/contracts/src/tokens/memecoin.cairo @@ -1,48 +1,32 @@ //! `UnruggableMemecoin` is an ERC20 token has additional features to prevent rug pulls. use starknet::ContractAddress; -#[starknet::interface] -trait IUnruggableMemecoin { - // ************************************ - // * Standard ERC20 functions - // ************************************ - fn name(self: @TState) -> felt252; - fn symbol(self: @TState) -> felt252; - fn decimals(self: @TState) -> u8; - fn total_supply(self: @TState) -> u256; - fn balance_of(self: @TState, account: ContractAddress) -> u256; - fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; - fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; - fn transfer_from( - ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; - // ************************************ - // * Additional functions - // ************************************ - fn launch_memecoin(ref self: TState); -} #[starknet::contract] mod UnruggableMemecoin { - // Core dependencies. - use openzeppelin::access::ownable::ownable::OwnableComponent::InternalTrait; use integer::BoundedInt; + use openzeppelin::access::ownable::OwnableComponent; + use openzeppelin::access::ownable::ownable::OwnableComponent::InternalTrait; + use openzeppelin::token::erc20::ERC20Component; use starknet::{ContractAddress, get_caller_address}; + use unruggable::tokens::interface::{ + IUnruggableMemecoinSnake, IUnruggableMemecoinCamel, IUnruggableAdditional + }; use zeroable::Zeroable; - // External dependencies. - use openzeppelin::access::ownable::OwnableComponent; - - // Internal dependencies. - use super::IUnruggableMemecoin; - // Components. component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); impl OwnableInternalImpl = OwnableComponent::InternalImpl; + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + // Internals + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + // ERC20 entrypoints. + #[abi(embed_v0)] + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + // Constants. - const DECIMALS: u8 = 18; /// The maximum number of holders allowed before launch. /// This is to prevent the contract from being launched with a large number of holders. /// Once reached, transfers are disabled until the memecoin is launched. @@ -56,38 +40,22 @@ mod UnruggableMemecoin { #[storage] struct Storage { marker_v_0: (), - name: felt252, - symbol: felt252, - total_supply: u256, - balances: LegacyMap, - allowances: LegacyMap<(ContractAddress, ContractAddress), u256>, // Components. #[substorage(v0)] - ownable: OwnableComponent::Storage + ownable: OwnableComponent::Storage, + #[substorage(v0)] + erc20: ERC20Component::Storage } #[event] #[derive(Drop, starknet::Event)] enum Event { - Transfer: Transfer, - Approval: Approval, #[flat] - OwnableEvent: OwnableComponent::Event + OwnableEvent: OwnableComponent::Event, + #[flat] + ERC20Event: ERC20Component::Event } - #[derive(Drop, starknet::Event)] - struct Transfer { - from: ContractAddress, - to: ContractAddress, - value: u256 - } - - #[derive(Drop, starknet::Event)] - struct Approval { - owner: ContractAddress, - spender: ContractAddress, - value: u256 - } /// Constructor called once when the contract is deployed. /// # Arguments @@ -106,20 +74,20 @@ mod UnruggableMemecoin { initial_supply: u256 ) { // Initialize the ERC20 token. - self.initializer(name, symbol); + self.erc20.initializer(name, symbol); // Initialize the owner. self.ownable.initializer(owner); // Mint initial supply to the initial recipient. - self._mint(initial_recipient, initial_supply); + self.erc20._mint(initial_recipient, initial_supply); } // // External // #[abi(embed_v0)] - impl UnruggableMemecoinImpl of IUnruggableMemecoin { + impl UnruggableEntrypoints of IUnruggableAdditional { // ************************************ // * UnruggableMemecoin functions // ************************************ @@ -130,34 +98,25 @@ mod UnruggableMemecoin { // Interactions. } + } + #[abi(embed_v0)] + impl SnakeEntrypoints of IUnruggableMemecoinSnake { // ************************************ - // * Standard ERC20 functions + // * snake_case functions // ************************************ - fn name(self: @ContractState) -> felt252 { - self.name.read() - } - - fn symbol(self: @ContractState) -> felt252 { - self.symbol.read() - } - - fn decimals(self: @ContractState) -> u8 { - DECIMALS - } - fn total_supply(self: @ContractState) -> u256 { - self.total_supply.read() + self.erc20.ERC20_total_supply.read() } fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { - self.balances.read(account) + self.erc20.ERC20_balances.read(account) } fn allowance( self: @ContractState, owner: ContractAddress, spender: ContractAddress ) -> u256 { - self.allowances.read((owner, spender)) + self.erc20.ERC20_allowances.read((owner, spender)) } fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { @@ -173,119 +132,52 @@ mod UnruggableMemecoin { amount: u256 ) -> bool { let caller = get_caller_address(); - self._spend_allowance(sender, caller, amount); - self._transfer(sender, recipient, amount); + self.erc20._spend_allowance(sender, caller, amount); + self.erc20._transfer(sender, recipient, amount); true } fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { let caller = get_caller_address(); - self._approve(caller, spender, amount); + self.erc20._approve(caller, spender, amount); true } } - #[external(v0)] - fn increase_allowance( - ref self: ContractState, spender: ContractAddress, added_value: u256 - ) -> bool { - self._increase_allowance(spender, added_value) - } - - #[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._decrease_allowance(spender, subtracted_value) - } - - #[external(v0)] - fn decreaseAllowance( - ref self: ContractState, spender: ContractAddress, subtractedValue: u256 - ) -> bool { - decrease_allowance(ref self, spender, subtractedValue) - } - - // - // Internal - // - - #[generate_trait] - impl UnruggableMemecoinInternalImpl of UnruggableMemecoinInternalTrait { - fn initializer(ref self: ContractState, name_: felt252, symbol_: felt252) { - self.name.write(name_); - self.symbol.write(symbol_); + #[abi(embed_v0)] + impl CamelEntrypoints of IUnruggableMemecoinCamel { + fn totalSupply(self: @ContractState) -> u256 { + self.erc20.ERC20_total_supply.read() } - - fn _increase_allowance( - ref self: ContractState, spender: ContractAddress, added_value: u256 - ) -> bool { - let caller = get_caller_address(); - self._approve(caller, spender, self.allowances.read((caller, spender)) + added_value); - true + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + self.erc20.ERC20_balances.read(account) } - - fn _decrease_allowance( - ref self: ContractState, spender: ContractAddress, subtracted_value: u256 + fn transferFrom( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 ) -> bool { let caller = get_caller_address(); - self - ._approve( - caller, spender, self.allowances.read((caller, spender)) - subtracted_value - ); + self.erc20._spend_allowance(sender, caller, amount); + self._transfer(sender, recipient, amount); true } + } - fn _mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { - assert(!recipient.is_zero(), 'ERC20: mint to 0'); - self.total_supply.write(self.total_supply.read() + amount); - self.balances.write(recipient, self.balances.read(recipient) + amount); - self.emit(Transfer { from: Zeroable::zero(), to: recipient, value: amount }); - } - - fn _burn(ref self: ContractState, account: ContractAddress, amount: u256) { - assert(!account.is_zero(), 'ERC20: burn from 0'); - self.total_supply.write(self.total_supply.read() - amount); - self.balances.write(account, self.balances.read(account) - amount); - self.emit(Transfer { from: account, to: Zeroable::zero(), value: amount }); - } - - fn _approve( - ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 - ) { - assert(!owner.is_zero(), 'ERC20: approve from 0'); - assert(!spender.is_zero(), 'ERC20: approve to 0'); - self.allowances.write((owner, spender), amount); - self.emit(Approval { owner, spender, value: amount }); - } + // + // Internal + // + #[generate_trait] + impl UnruggableMemecoinInternalImpl of UnruggableMemecoinInternalTrait { fn _transfer( ref self: ContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256 ) { - assert(!sender.is_zero(), 'ERC20: transfer from 0'); - assert(!recipient.is_zero(), 'ERC20: transfer to 0'); - self.balances.write(sender, self.balances.read(sender) - amount); - self.balances.write(recipient, self.balances.read(recipient) + amount); - self.emit(Transfer { from: sender, to: recipient, value: amount }); - } - - fn _spend_allowance( - ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 - ) { - let current_allowance = self.allowances.read((owner, spender)); - if current_allowance != BoundedInt::max() { - self._approve(owner, spender, current_allowance - amount); - } + self.erc20._transfer(sender, recipient, amount); } } } diff --git a/contracts/tests/test_unruggable_memecoin.cairo b/contracts/tests/test_unruggable_memecoin.cairo index b0e670b8..78051e98 100644 --- a/contracts/tests/test_unruggable_memecoin.cairo +++ b/contracts/tests/test_unruggable_memecoin.cairo @@ -1,10 +1,10 @@ use core::debug::PrintTrait; use core::traits::Into; -use starknet::{ContractAddress, contract_address_const}; use openzeppelin::token::erc20::interface::IERC20; use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank}; +use starknet::{ContractAddress, contract_address_const}; -use unruggable::tokens::memecoin::{ +use unruggable::tokens::interface::{ IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait }; @@ -27,21 +27,306 @@ fn deploy_contract( contract.deploy(@constructor_calldata).unwrap() } -#[test] -fn test_mint() { - let owner = contract_address_const::<42>(); - let initial_supply = 1000.into(); - let contract_address = deploy_contract( - owner, owner, 'UnruggableMemecoin', 'MT', initial_supply - ); +mod erc20_metadata { + use core::debug::PrintTrait; + use openzeppelin::token::erc20::interface::IERC20; + use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank, CheatTarget}; + use starknet::{ContractAddress, contract_address_const}; + use super::deploy_contract; + use unruggable::tokens::interface::{ + IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait + }; + + #[test] + fn test_name() { + let owner = contract_address_const::<42>(); + let recipient = contract_address_const::<43>(); + let name = 'UnruggableMemecoin'; + let symbol = 'UM'; + let initial_supply = 1000.into(); + let contract_address = deploy_contract(owner, recipient, name, symbol, initial_supply); + + let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + + // Check name. Should be equal to 'UnruggableMemecoin'. + let name = memecoin.name(); + assert(name == name, 'Invalid name'); + } + + #[test] + fn test_decimals() { + let owner = contract_address_const::<42>(); + let recipient = contract_address_const::<43>(); + let name = 'UnruggableMemecoin'; + let symbol = 'UM'; + let initial_supply = 1000.into(); + let contract_address = deploy_contract(owner, recipient, name, symbol, initial_supply); + + let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + + // Check decimals. Should be equal to 18. + let decimals = memecoin.decimals(); + assert(decimals == 18, 'Invalid decimals'); + } + + #[test] + fn test_symbol() { + let owner = contract_address_const::<42>(); + let recipient = contract_address_const::<43>(); + let name = 'UnruggableMemecoin'; + let symbol = 'UM'; + let initial_supply = 1000.into(); + let contract_address = deploy_contract(owner, recipient, name, symbol, initial_supply); + + let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + + // Check symbol. Should be equal to 'UM'. + let symbol = memecoin.symbol(); + assert(symbol == symbol, 'Invalid symbol'); + } +} + +mod erc20_entrypoints { + use core::debug::PrintTrait; + use core::traits::Into; + use openzeppelin::token::erc20::interface::IERC20; + use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank, CheatTarget}; + use starknet::{ContractAddress, contract_address_const}; + use super::deploy_contract; + use unruggable::tokens::interface::{ + IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait + }; + + // Test ERC20 snake entrypoints + + #[test] + fn test_total_supply() { + let owner = contract_address_const::<42>(); + let initial_supply = 1000.into(); + let contract_address = deploy_contract( + owner, owner, 'UnruggableMemecoin', 'UM', initial_supply + ); + + let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + + // Check total supply. Should be equal to initial supply. + let total_supply = memecoin.total_supply(); + assert(total_supply == initial_supply, 'Invalid total supply'); + + // Check initial balance. Should be equal to initial supply. + let balance = memecoin.balance_of(owner); + assert(balance == initial_supply, 'Invalid balance'); + } + + + #[test] + fn test_balance_of() { + let owner = contract_address_const::<42>(); + let initial_supply = 1000.into(); + let contract_address = deploy_contract( + owner, owner, 'UnruggableMemecoin', 'UM', initial_supply + ); + + let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + + // Check initial balance. Should be equal to initial supply. + let balance = memecoin.balance_of(owner); + assert(balance == initial_supply, 'Invalid balance'); + } + + #[test] + fn test_approve_allowance() { + let owner = contract_address_const::<42>(); + let spender = contract_address_const::<43>(); + let initial_supply = 1000.into(); + let contract_address = deploy_contract( + owner, owner, 'UnruggableMemecoin', 'UM', initial_supply + ); + + let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + + // Check initial allowance. Should be equal to 0. + let allowance = memecoin.allowance(owner, spender); + assert(allowance == 0.into(), 'Invalid allowance before'); + + // Approve initial supply tokens. + start_prank(CheatTarget::One(memecoin.contract_address), owner); + memecoin.approve(spender, initial_supply); + + // Check allowance. Should be equal to initial supply. + let allowance = memecoin.allowance(owner, spender); + assert(allowance == initial_supply, 'Invalid allowance after'); + } + + #[test] + fn test_transfer() { + let owner = contract_address_const::<42>(); + let recipient = contract_address_const::<43>(); + let initial_supply = 1000.into(); + let contract_address = deploy_contract( + owner, owner, 'UnruggableMemecoin', 'UM', initial_supply + ); + + let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + + // Transfer 100 tokens to recipient. + start_prank(CheatTarget::One(memecoin.contract_address), owner); + memecoin.transfer(recipient, 100.into()); + + // Check balance. Should be equal to initial supply - 100. + let owner_balance = memecoin.balance_of(owner); + assert(owner_balance == (initial_supply - 100.into()), 'Invalid balance owner'); + + // Check recipient balance. Should be equal to 100. + let recipient_balance = memecoin.balance_of(recipient); + assert(recipient_balance == 100.into(), 'Invalid balance recipient'); + } + + #[test] + fn test_transfer_from() { + let owner = contract_address_const::<42>(); + let spender = contract_address_const::<43>(); + let recipient = contract_address_const::<44>(); + let initial_supply = 1000.into(); + let contract_address = deploy_contract( + owner, owner, 'UnruggableMemecoin', 'UM', initial_supply + ); + + let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + + // Check initial balance. Should be equal to initial supply. + let balance = memecoin.balance_of(owner); + assert(balance == initial_supply, 'Invalid balance'); + + // Approve initial supply tokens. + start_prank(CheatTarget::One(memecoin.contract_address), owner); + memecoin.approve(spender, initial_supply); + + // Transfer 100 tokens to recipient. + start_prank(CheatTarget::One(memecoin.contract_address), spender); + memecoin.transfer_from(owner, recipient, 100.into()); + + // Check balance. Should be equal to initial supply - 100. + let owner_balance = memecoin.balance_of(owner); + assert(owner_balance == (initial_supply - 100.into()), 'Invalid balance owner'); + + // Check recipient balance. Should be equal to 100. + let recipient_balance = memecoin.balance_of(recipient); + assert(recipient_balance == 100.into(), 'Invalid balance recipient'); + + // Check allowance. Should be equal to initial supply - 100. + let allowance = memecoin.allowance(owner, spender); + assert(allowance == (initial_supply - 100.into()), 'Invalid allowance'); + } + + // Test ERC20 Camel entrypoints + + #[test] + fn test_totalSupply() { + let owner = contract_address_const::<42>(); + let initial_supply = 1000.into(); + let contract_address = deploy_contract( + owner, owner, 'UnruggableMemecoin', 'UM', initial_supply + ); + + let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + + // Check total supply. Should be equal to initial supply. + let total_supply = memecoin.totalSupply(); + assert(total_supply == initial_supply, 'Invalid total supply'); + } + + #[test] + fn test_balanceOf() { + let owner = contract_address_const::<42>(); + let initial_supply = 1000.into(); + let contract_address = deploy_contract( + owner, owner, 'UnruggableMemecoin', 'UM', initial_supply + ); + + let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + + // Check initial balance. Should be equal to initial supply. + let balance = memecoin.balanceOf(owner); + assert(balance == initial_supply, 'Invalid balance'); + } + + #[test] + fn test_transferFrom() { + let owner = contract_address_const::<42>(); + let spender = contract_address_const::<43>(); + let recipient = contract_address_const::<44>(); + let initial_supply = 1000.into(); + let contract_address = deploy_contract( + owner, owner, 'UnruggableMemecoin', 'UM', initial_supply + ); + + let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + + // Check initial balance. Should be equal to initial supply. + let balance = memecoin.balanceOf(owner); + assert(balance == initial_supply, 'Invalid balance'); + + // Approve initial supply tokens. + start_prank(CheatTarget::One(memecoin.contract_address), owner); + memecoin.approve(spender, initial_supply); + + // Transfer 100 tokens to recipient. + start_prank(CheatTarget::One(memecoin.contract_address), spender); + memecoin.transferFrom(owner, recipient, 100.into()); + + // Check balance. Should be equal to initial supply - 100. + let balance = memecoin.balanceOf(owner); + assert(balance == (initial_supply - 100.into()), 'Invalid balance'); + + // Check recipient balance. Should be equal to 100. + let balance = memecoin.balanceOf(recipient); + assert(balance == 100.into(), 'Invalid balance'); + + // Check allowance. Should be equal to initial supply - 100. + let allowance = memecoin.allowance(owner, spender); + assert(allowance == (initial_supply - 100.into()), 'Invalid allowance'); + } +} + +mod memecoin_entrypoints { + use core::debug::PrintTrait; + use openzeppelin::token::erc20::interface::IERC20; + use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank, CheatTarget}; + use starknet::{ContractAddress, contract_address_const}; + use super::deploy_contract; + use unruggable::tokens::interface::{ + IUnruggableMemecoinDispatcher, IUnruggableMemecoinDispatcherTrait + }; + + #[test] + fn test_launch_memecoin() { + let owner = contract_address_const::<42>(); + let recipient = contract_address_const::<43>(); + let initial_supply = 1000.into(); + let contract_address = deploy_contract( + owner, recipient, 'UnruggableMemecoin', 'UM', initial_supply + ); + + let memecoin = IUnruggableMemecoinDispatcher { contract_address }; + + start_prank(CheatTarget::One(memecoin.contract_address), owner); + memecoin.launch_memecoin(); + //TODO + } - let safe_dispatcher = IUnruggableMemecoinDispatcher { contract_address }; + #[test] + #[should_panic(expected: ('Caller is not the owner',))] + fn test_launch_memecoin_not_owner() { + let owner = contract_address_const::<42>(); + let recipient = contract_address_const::<43>(); + let initial_supply = 1000.into(); + let contract_address = deploy_contract( + owner, recipient, 'UnruggableMemecoin', 'UM', initial_supply + ); - // Check total supply. Should be equal to initial supply. - let total_supply = safe_dispatcher.total_supply(); - assert(total_supply == initial_supply, 'Invalid total supply'); + let memecoin = IUnruggableMemecoinDispatcher { contract_address }; - // Check initial balance. Should be equal to initial supply. - let balance = safe_dispatcher.balance_of(owner); - assert(balance == initial_supply, 'Invalid balance'); + memecoin.launch_memecoin(); + } } diff --git a/frontend/package.json b/frontend/package.json index 96c2620d..59f599be 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,7 +17,7 @@ "@vanilla-extract/css": "^1.11.0", "@vanilla-extract/recipes": "^0.4.0", "@vanilla-extract/sprinkles": "^1.6.0", - "clsx": "^1.2.1", + "clsx": "^2.0.0", "lucide-react": "^0.294.0", "ms.macro": "^2.0.0", "polished": "^4.2.2", diff --git a/frontend/public/index.html b/frontend/public/index.html index 4f96f3f8..38418a21 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -2,10 +2,11 @@ - - + + +