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
+
+
+
+
+
+
+
+
+
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 💻
Charles Lanier 💻
Francesco Ceccon 💻
+ Mathieu 💻
+ 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 @@
-
-
+
+
+