From 5dcf0717c29fc6d31441f062543ef25da0df4212 Mon Sep 17 00:00:00 2001 From: Dev Esther <125284347+estheroche@users.noreply.github.com> Date: Fri, 2 Feb 2024 19:30:24 -0600 Subject: [PATCH] chore: add testing ERC20 contract (#316) Co-authored-by: gianalarcon --- src/ch02-13-foundry-forge.md | 797 +++++++++++++++++++++++++---------- yarn.lock | 776 +++++++++++++++++----------------- 2 files changed, 970 insertions(+), 603 deletions(-) diff --git a/src/ch02-13-foundry-forge.md b/src/ch02-13-foundry-forge.md index 9fe1b55b7..a33d9a1a2 100644 --- a/src/ch02-13-foundry-forge.md +++ b/src/ch02-13-foundry-forge.md @@ -8,10 +8,10 @@ To utilize Forge, define test functions and label them with test attributes. Use This section guides you through the Starknet Foundry `snforge` command-line tool. Learn how to set up a new project, compile the code, and execute tests. -To start a new project with Starknet Foundry, use the `--init` command and replace `project_name` with your project's name. +To start a new project with Starknet Foundry, use the `snforge init` command and replace `project_name` with your project's name. ```shell -snforge --init project_name +snforge init project_name ``` Once you've set up the project, inspect its layout: @@ -35,26 +35,47 @@ The project structure is as follows: - `tests/` is the location of your test files. - `Scarb.toml` is for project and **`snforge`** configurations. -Ensure the CASM code generation is active in the `Scarb.toml` file: +Ensure the CASM and SIERRA code generation is active in the `Scarb.toml` file: ```shell # ... [[target.starknet-contract]] casm = true +sierra = true # ... ``` -To run tests using `snforge`: +### Requirements for snforge + +Before you run `snforge test` certain prerequisites must be addressed: + +1. Install the lastest [scarb version](#https://docs.swmansion.com/scarb/docs.html). +2. Install [starknet-foundry](#https://foundry-rs.github.io/starknet-foundry/getting-started/installation.html) by running this command: + +`curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh +` + +Follow the instructions and then run: +`snfoundryup` + +3. Check your `snforge` version, run : + `snforge version` + +As athe time of this tutorial, we used `snforge` version `snforge 0.16.0` which is the lastest at this time. + +### Test + +Run tests using `snforge test`: ```shell snforge -Collected 2 test(s) from the `test_name` package -Running 0 test(s) from `src/` -Running 2 test(s) from `tests/` -[PASS] tests::test_contract::test_increase_balance -[PASS] tests::test_contract::test_cannot_increase_balance_with_zero_value -Tests: 2 passed, 0 failed, 0 skipped +Collected 2 test(s) from tesing package +Running 0 test(s) from src/ +Running 2 test(s) from tests/ +[PASS] tests::test_contract::test_cannot_increase_balance_with_zero_value (gas: ~1839) +[PASS] tests::test_contract::test_increase_balance (gas: ~3065) +Tests: 2 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out ``` ## Integrating `snforge` with Existing Scarb Projects @@ -64,7 +85,7 @@ For those with an established Scarb project who wish to incorporate `snforge`, e ```shell # ... [dependencies] -snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "[VERSION]" } +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry", tag = "v0.16.0" } ``` Ensure the tag version corresponds with your `snforge` version. To verify your `snforge` version: @@ -76,14 +97,14 @@ snforge --version Or, add this dependency using the `scarb` command: ```shell -scarb add snforge_std --git https://github.com/foundry-rs/starknet-foundry.git --tag VERSION +scarb add snforge_std --git https://github.com/foundry-rs/starknet-foundry.git --tag v0.16.0 ``` With these steps, your existing Scarb project is now **`snforge`**-ready. ## Testing with `snforge` -Utilize Starknet Foundry's `snforge` command to efficiently run tests. +Utilize Starknet Foundry's `snforge test` command to efficiently run tests. ### Executing Tests @@ -96,19 +117,20 @@ snforge Sample output might resemble: ```shell -Collected 3 test(s) from `package_name` package -Running 3 test(s) from `src/` -[PASS] package_name::executing -[PASS] package_name::calling -[PASS] package_name::calling_another -Tests: 3 passed, 0 failed, 0 skipped + +Collected 2 test(s) from tesingg package +Running 0 test(s) from src/ +Running 2 test(s) from tests/ +[PASS] tests::test_contract::test_cannot_increase_balance_with_zero_value (gas: ~1839) +[PASS] tests::test_contract::test_increase_balance (gas: ~3065) +Tests: 2 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out ``` ## Example: Testing a Simple Contract The example provided below demonstrates how to test a Starknet contract using `snforge`. -```rust +``` #[starknet::interface] trait IHelloStarknet { fn increase_balance(ref self: TContractState, amount: felt252); @@ -122,20 +144,19 @@ mod HelloStarknet { balance: felt252, } - #[external(v0)] + #[abi(embed_v0)] impl HelloStarknetImpl of super::IHelloStarknet { - // Increases the balance by the specified amount. fn increase_balance(ref self: ContractState, amount: felt252) { + assert(amount != 0, 'Amount cannot be 0'); self.balance.write(self.balance.read() + amount); } - // Returns the balance. - fn get_balance(self: @ContractState) -> felt252 { self.balance.read() } } } + ``` Remember, the identifier following `mod` signifies the contract name. Here, the contract name is `HelloStarknet`. @@ -144,38 +165,65 @@ Remember, the identifier following `mod` signifies the contract name. Here, the Below is a test for the **`HelloStarknet`** contract. This test deploys **`HelloStarknet`** and interacts with its functions: -```rust -use snforge_std::{ declare, ContractClassTrait }; +``` +use starknet::ContractAddress; + +use snforge_std::{declare, ContractClassTrait}; + +use tesingg::IHelloStarknetSafeDispatcher; +use tesingg::IHelloStarknetSafeDispatcherTrait; +use tesingg::IHelloStarknetDispatcher; +use tesingg::IHelloStarknetDispatcherTrait; + +fn deploy_contract(name: felt252) -> ContractAddress { + let contract = declare(name); + contract.deploy(@ArrayTrait::new()).unwrap() +} #[test] -fn call_and_invoke() { - // Declare and deploy the contract - let contract = declare('HelloStarknet'); - let contract_address = contract.deploy(@ArrayTrait::new()).unwrap(); +fn test_increase_balance() { + let contract_address = deploy_contract('HelloStarknet'); - // Instantiate a Dispatcher object for contract interactions let dispatcher = IHelloStarknetDispatcher { contract_address }; - // Invoke a contract's view function - let balance = dispatcher.get_balance(); - assert(balance == 0, 'balance == 0'); + let balance_before = dispatcher.get_balance(); + assert(balance_before == 0, 'Invalid balance'); - // Invoke another function to modify the storage state - dispatcher.increase_balance(100); + dispatcher.increase_balance(42); - // Validate the transaction's effect - let balance = dispatcher.get_balance(); - assert(balance == 100, 'balance == 100'); + let balance_after = dispatcher.get_balance(); + assert(balance_after == 42, 'Invalid balance'); +} + +#[test] +fn test_cannot_increase_balance_with_zero_value() { + let contract_address = deploy_contract('HelloStarknet'); + + let safe_dispatcher = IHelloStarknetSafeDispatcher { contract_address }; + + #[feature("safe_dispatcher")] + let balance_before = safe_dispatcher.get_balance().unwrap(); + assert(balance_before == 0, 'Invalid balance'); + + #[feature("safe_dispatcher")] + match safe_dispatcher.increase_balance(0) { + Result::Ok(_) => panic_with_felt252('Should have panicked'), + Result::Err(panic_data) => { + assert(*panic_data.at(0) == 'Amount cannot be 0', *panic_data.at(0)); + } + }; } ``` To run the test, execute the `snforge` command. The expected output is: ```shell -Collected 1 test(s) from using_dispatchers package -Running 1 test(s) from src/ -[PASS] using_dispatchers::call_and_invoke -Tests: 1 passed, 0 failed, 0 skipped +Collected 2 test(s) from tesing package +Running 0 test(s) from src/ +Running 2 test(s) from tests/ +[PASS] tests::test_contract::test_cannot_increase_balance_with_zero_value (gas: ~1839) +[PASS] tests::test_contract::test_increase_balance (gas: ~3065) +Tests: 2 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out ``` ## Example: Testing ERC20 Contract @@ -184,58 +232,257 @@ There are several methods to test smart contracts, such as unit tests, integrati ## ERC20 Contract Example -After setting up your foundry project, add the following dependency to your `Scarb.toml` (in this case we are using version 0.7.0 of the OpenZeppelin Cairo contracts, but you can use any version you want): +After setting up your foundry project, add the following dependency to your `Scarb.toml` (in this case we are using version 0.8.0 of the OpenZeppelin Cairo contracts, due to the fact that it uses components): ```shell -openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.7.0" } +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.8.1" } ``` Here's a basic ERC20 contract: -```rust +``` use starknet::ContractAddress; - #[starknet::interface] -trait Ierc20 { +trait IERC20 { + fn get_name(self: @TContractState) -> felt252; + fn get_symbol(self: @TContractState) -> felt252; + fn get_decimals(self: @TContractState) -> u8; + fn get_total_supply(self: @TContractState) -> u256; fn balance_of(self: @TContractState, account: ContractAddress) -> u256; - fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; + fn allowance(self: @TContractState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: u256); + fn transfer_from( + ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ); + fn approve(ref self: TContractState, spender: ContractAddress, amount: u256); + fn increase_allowance(ref self: TContractState, spender: ContractAddress, added_value: u256); + fn decrease_allowance( + ref self: TContractState, spender: ContractAddress, subtracted_value: u256 + ); + + + fn mint(ref self: TContractState, recipient: ContractAddress, amount: u256) -> bool; } #[starknet::contract] -mod erc20 { +mod ERC20Token { + +Importing necessary libraries + use starknet::ContractAddress; - use openzeppelin::token::erc20::ERC20; + use starknet::get_caller_address; + use starknet::contract_address_const; +Similar to address(0) in Solidity + + use core::zeroable::Zeroable; + use super::IERC20; + + //Stroge Variables #[storage] - struct Storage {} + struct Storage { + name: felt252, + symbol: felt252, + decimals: u8, + total_supply: u256, + balances: LegacyMap, + allowances: LegacyMap< + (ContractAddress, ContractAddress), u256 + >, //similar to mapping(address => mapping(address => uint256)) + } + // Event + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Approval: Approval, + Transfer: Transfer + } + + #[derive(Drop, starknet::Event)] + struct Transfer { + from: ContractAddress, + to: ContractAddress, + value: u256 + } + + #[derive(Drop, starknet::Event)] + struct Approval { + owner: ContractAddress, + spender: ContractAddress, + value: u256, + } + +The contract constructor is not part of the interface. Nor are internal functions part of the interface. + +Constructor #[constructor] - fn constructor( - ref self: ContractState, - initial_supply: felt252, - recipient: ContractAddress - ) { - let name = 'MyToken'; - let symbol = 'MTK'; - - let mut unsafe_state = ERC20::unsafe_new_contract_state(); - ERC20::InternalImpl::initializer(ref unsafe_state, name, symbol); - ERC20::InternalImpl::_mint(ref unsafe_state, recipient, initial_supply.into()); + fn constructor(ref self: ContractState, // _name: felt252, + + 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('ERC20Token'); + self.symbol.write('ECT'); + 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: 1000000 + } + ); } - #[external(v0)] - impl Ierc20Impl of super::Ierc20 { + #[abi(embed_v0)] + impl IERC20Impl of IERC20 { + fn get_name(self: @ContractState) -> felt252 { + self.name.read() + } + fn get_symbol(self: @ContractState) -> felt252 { + self.symbol.read() + } + + fn get_decimals(self: @ContractState) -> u8 { + self.decimals.read() + } + + fn get_total_supply(self: @ContractState) -> u256 { + self.total_supply.read() + } + + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { - let unsafe_state = ERC20::unsafe_new_contract_state(); - ERC20::ERC20Impl::balance_of(@unsafe_state, account) + self.balances.read(account) + } + + fn allowance( + self: @ContractState, owner: ContractAddress, spender: ContractAddress + ) -> u256 { + self.allowances.read((owner, spender)) + } + + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { + let owner = self.owner.read(); + let caller = get_caller_address(); + assert(owner == caller, Errors::CALLER_NOT_OWNER); + assert(!recipient.is_zero(), Errors::ADDRESS_ZERO); + assert(self.balances.read(recipient) >= amount, Errors::INSUFFICIENT_FUND); + self.balances.write(recipient, self.balances.read(recipient) + amount); + self.total_supply.write(self.total_supply.read() - amount); + // call tranfer + // Transfer(Zeroable::zero(), recipient, amount); + + true + } + + + fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) { + let caller = get_caller_address(); + self.transfer_helper(caller, recipient, amount); + } + + fn transfer_from( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + let caller = get_caller_address(); + 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 + ); //responsible for deduction of the amount allowed to spend + self.transfer_helper(sender, recipient, amount); + } + + fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) { + let caller = get_caller_address(); + self.approve_helper(caller, spender, amount); + } + + fn increase_allowance( + ref self: ContractState, spender: ContractAddress, added_value: u256 + ) { + let caller = get_caller_address(); + self + .approve_helper( + caller, spender, self.allowances.read((caller, spender)) + added_value + ); } - fn transfer(ref self: ContractState, recipient: ContractAddress, amount: u256) -> bool { - let mut unsafe_state = ERC20::unsafe_new_contract_state(); - ERC20::ERC20Impl::transfer(ref unsafe_state, recipient, amount) + fn decrease_allowance( + ref self: ContractState, spender: ContractAddress, subtracted_value: u256 + ) { + let caller = get_caller_address(); + self + .approve_helper( + caller, spender, self.allowances.read((caller, spender)) - subtracted_value + ); + } + } + + #[generate_trait] + impl HelperImpl of HelperTrait { + fn transfer_helper( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + let sender_balance = self.balance_of(sender); + + assert(!sender.is_zero(), 'transfer from 0'); + assert(!recipient.is_zero(), 'transfer to 0'); + assert(sender_balance >= amount, 'Insufficient fund'); + self.balances.write(sender, self.balances.read(sender) - amount); + self.balances.write(recipient, self.balances.read(recipient) + amount); + true; + + self.emit(Transfer { from: sender, to: recipient, value: amount, }); + } + + fn approve_helper( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + assert(!owner.is_zero(), 'approve from 0'); + assert(!spender.is_zero(), 'approve to 0'); + + self.allowances.write((owner, spender), amount); + + self.emit(Approval { owner, spender, value: amount, }) + } + + fn spend_allowance( + ref self: ContractState, owner: ContractAddress, spender: ContractAddress, amount: u256 + ) { + // First, read the amount authorized by owner to spender + let current_allowance = self.allowances.read((owner, spender)); + + // define a variable ONES_MASK of type u128 + let ONES_MASK = 0xfffffffffffffffffffffffffffffff_u128; + + // to determine whether the authorization is unlimited, + + let is_unlimited_allowance = current_allowance.low == ONES_MASK + && current_allowance + .high == ONES_MASK; //equivalent to type(uint256).max in Solidity. + + // This is also a way to save gas, because if the authorized amount is the maximum value of u256, theoretically, this amount cannot be spent. + if !is_unlimited_allowance { + self.approve_helper(owner, spender, current_allowance - amount); + } } } -} ``` This contract allows minting tokens to a recipient during deployment, checking balances, and transferring tokens, relying on the openzeppelin ERC20 library. @@ -244,191 +491,310 @@ This contract allows minting tokens to a recipient during deployment, checking b Organize your test file and include the required imports: -```rust +``` #[cfg(test)] -mod tests { - use array::ArrayTrait; - use result::ResultTrait; - use option::OptionTrait; - use traits::TryInto; +mod test { + use core::serde::Serde; + use super::{IERC20, ERC20Token, IERC20Dispatcher, IERC20DispatcherTrait}; use starknet::ContractAddress; - use starknet::Felt252TryIntoContractAddress; - use snforge_std::{declare, ContractClassTrait}; - // Additional code here. + use starknet::contract_address::contract_address_const; + use core::array::ArrayTrait; + use snforge_std::{declare, ContractClassTrait, fs::{FileTrait, read_txt}}; + use snforge_std::{start_prank, stop_prank, CheatTarget}; + use snforge_std::PrintTrait; + use core::traits::{Into, TryInto}; } ``` -For testing, you'll need a helper function to deploy the contract instance. This function requires a `supply` amount and `recipient` address: +For testing, you'll need a helper function to deploy the contract instance. -```rust -use snforge_std::{declare, ContractClassTrait}; +This function requires a `supply` amount and `recipient` address: + +Before deploying a starknet contract, we need a contract_class. + +Get it using the declare function from [starknet Foundry](#https://foundry-rs.github.io/starknet-foundry/) + +Supply values the constructor arguements when deploying -fn deploy_contract(name: felt252) -> ContractAddress { - let recipient = starknet::contract_address_const::<0x01>(); - let supply: felt252 = 20000000; - let contract = declare(name); - let mut calldata = array![supply, recipient.into()]; - contract.deploy(@calldata).unwrap() -} -// Additional code here. ``` -Use `declare` and `ContractClassTrait` from `snforge_std`. Then, initialize the `supply` and `recipient`, declare the contract, compute the calldata, and deploy. + fn deploy_contract() -> ContractAddress { + let erc20contract_class = declare(' + ERC20Token'); + let file = FileTrait::new('data/constructor_args.txt'); + let constructor_args = read_txt(@file); + let contract_address = erc20contract_class.deploy(@constructor_args).unwrap(); + contract_address + } -### Writing the Test Cases +``` -#### Verifying the Balance After Deployment +Generate an address -To begin, test the deployment helper function to confirm the recipient's balance: +``` -```rust - // ... - use erc20_contract::erc20::Ierc20SafeDispatcher; - use erc20_contract::erc20::Ierc20SafeDispatcherTrait; + mod Account { + use starknet::ContractAddress; + use core::traits::TryInto; - #[test] - #[available_gas(3000000000000000)] - fn test_balance_of() { - let contract_address = deploy_contract('erc20'); - let safe_dispatcher = Ierc20SafeDispatcher { contract_address }; - let recipient = starknet::contract_address_const::<0x01>(); - let balance = safe_dispatcher.balance_of(recipient).unwrap(); - assert(balance == 20000000, 'Invalid Balance'); + fn User1() -> ContractAddress { + 'user1'.try_into().unwrap() + } + fn User2() -> ContractAddress { + 'user2'.try_into().unwrap() + } + + fn admin() -> ContractAddress { + 'admin'.try_into().unwrap() + } } + ``` -Execute `snforge` to verify: +Use `declare` and `ContractClassTrait` from `snforge_std`. Then, initialize the `supply` and `recipient`, declare the contract, compute the calldata, and deploy. + +### Writing the Test Cases + +### Verifying the contract details After Deployment using Fuzz testing + +To begin, test the deployment helper function to confirm the details provided: -```shell -Collected 1 test from erc20_contract package -[PASS] tests::test_erc20::test_balance_of ``` +#[test] + fn test_constructor() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + // let name = dispatcher.get_name(); + let name = dispatcher.get_name(); -#### Utilizing Foundry Cheat Codes + assert(name == 'ERC20Token', 'name is not correct'); + } -When testing smart contracts, simulating different conditions is essential. `Foundry Cheat Codes` from the `snforge_std` library offer these simulation capabilities for Starknet smart contracts. + #[test] + fn test_decimal_is_correct() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + let decimal = dispatcher.get_decimals(); -These cheat codes consist of helper functions that adjust the smart contract's environment. They allow developers to modify parameters or conditions to examine contract behavior in specific scenarios. + assert(decimal == 18, 'Decimal is not correct'); + } -Using `snforge_std`'s cheat codes, you can change elements like block numbers, timestamps, or even the caller of a function. This guide focuses on `start_prank` and `stop_prank`. You can find a reference to available cheat codes [here](https://foundry-rs.github.io/starknet-foundry/appendix/cheatcodes.html) + #[test] + fn test_total_supply() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + let total_supply = dispatcher.get_total_supply(); -Below is a transfer test example: + assert(total_supply == 1000000, 'Total supply is wrong'); + } -```rust - use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank}; + #[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()); + 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] + #[fuzzer(runs: 22, seed: 38)] + fn test_allowance(amount: u256) { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; + + start_prank(CheatTarget::One(contract_address), Account::admin()); + dispatcher.approve(contract_address, 20); + + let currentAllowance = dispatcher.allowance(Account::admin(), contract_address); + + assert(currentAllowance == 20, Errors::NOT_ALLOWED); + stop_prank(CheatTarget::One(contract_address)); + } #[test] - #[available_gas(3000000000000000)] fn test_transfer() { - let contract_address = deploy_contract('erc20'); - let safe_dispatcher = Ierc20SafeDispatcher { contract_address }; + 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()); - let sender = starknet::contract_address_const::<0x01>(); - let receiver = starknet::contract_address_const::<0x02>(); - let amount : felt252 = 10000000; + start_prank(CheatTarget::One(contract_address), Account::admin()); - // Set the function's caller - start_prank(contract_address, sender); - safe_dispatcher.transfer(receiver.into(), amount.into()); + dispatcher.transfer(Account::user1(), 50); - let balance_after_transfer = safe_dispatcher.balance_of(receiver).unwrap(); - assert(balance_after_transfer == 10000000, 'Incorrect Amount'); + // Confirm that the funds have been sent! + assert( + dispatcher.balance_of(Account::admin()) == original_sender_balance - 50, + Errors::FUNDS_NOT_SENT + ); - // End the prank - stop_prank(contract_address); + // 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)); } -``` -Executing `snforge` for the tests displays: -```shell -Collected 2 tests from erc20_contract package -[PASS] tests::test_erc20::test_balance_of -[PASS] tests::test_erc20::test_transfer -``` + #[test] + fn test_transfer_from() { + 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::NOT_ALLOWED); + + 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)); + } -In this example, `start_prank` determines the transfer function's caller, while `stop_prank` concludes the prank. + #[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); + } -
-Full `ERC20 test example` file - #[cfg(test)] - mod tests { - use array::ArrayTrait; - use result::ResultTrait; - use option::OptionTrait; - use traits::TryInto; - use starknet::ContractAddress; - use starknet::Felt252TryIntoContractAddress; - - use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank}; - use erc20_contract::erc20::Ierc20SafeDispatcher; - use erc20_contract::erc20::Ierc20SafeDispatcherTrait; - - fn deploy_contract(name: felt252) -> ContractAddress { - let recipient = starknet::contract_address_const::<0x01>(); - let supply : felt252 = 20000000; - let contract = declare(name); - let mut calldata = array![supply, recipient.into()]; - contract.deploy(@calldata).unwrap() - } + #[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); + } - #[test] - #[available_gas(3000000000000000)] - fn test_balance_of() { - let contract_address = deploy_contract('erc20'); - let safe_dispatcher = Ierc20SafeDispatcher { contract_address }; - let recipient = starknet::contract_address_const::<0x01>(); - let balance = safe_dispatcher.balance_of(recipient).unwrap(); - assert(balance == 20000000, 'Invalid Balance'); - } + #[test] + fn test_approve() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; - #[test] - #[available_gas(3000000000000000)] - fn test_transfer() { - let contract_address = deploy_contract('erc20'); - let safe_dispatcher = Ierc20SafeDispatcher { contract_address }; - - let sender = starknet::contract_address_const::<0x01>(); - let receiver = starknet::contract_address_const::<0x02>(); - let amount : felt252 = 10000000; - - start_prank(contract_address, sender); - safe_dispatcher.transfer(receiver.into(), amount.into()); - let balance_after_transfer = safe_dispatcher.balance_of(receiver).unwrap(); - assert(balance_after_transfer == 10000000, 'Incorrect Amount'); - stop_prank(contract_address); - } - } + start_prank(CheatTarget::One(contract_address), Account::admin()); + dispatcher.approve(Account::user1(), 50); + assert(dispatcher.allowance(Account::admin(), Account::user1()) == 50, Errors::NOT_ALLOWED); + } -
+ #[test] + fn test_increase_allowance() { + let contract_address = deploy_contract(); + let dispatcher = IERC20Dispatcher { contract_address }; -## Fuzz Testing + start_prank(CheatTarget::One(contract_address), Account::admin()); + dispatcher.approve(Account::user1(), 30); + assert(dispatcher.allowance(Account::admin(), Account::user1()) == 30, Errors::NOT_ALLOWED); -Fuzz testing introduces random inputs to the code to identify vulnerabilities, security issues, and unforeseen behaviors. While you can manually provide these inputs, automation is preferable when testing a broad set of values. See the example below in `test_fuzz.cairo`: + dispatcher.increase_allowance(Account::user1(), 20); -```rust - fn mul(a: felt252, b: felt252) -> felt252 { - return a * b; + assert( + dispatcher.allowance(Account::admin(), Account::user1()) == 50, + Errors::ERROR_INCREASING_ALLOWANCE + ); } #[test] - fn test_fuzz_sum(x: felt252, y: felt252) { - assert(mul(x, y) == x * y, 'incorrect'); + 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::NOT_ALLOWED); + + dispatcher.decrease_allowance(Account::user1(), 5); + + assert( + dispatcher.allowance(Account::admin(), Account::user1()) == 25, + Errors::ERROR_DECREASING_ALLOWANCE + ); } ``` -Running `snforge` produces: +Running `snforge test` produces: ```shell - Collected 1 test(s) from erc20_contract package + Collected 12 test(s) from te package +Running 12 test(s) from src/ +[PASS] testing::ERC20Token::test::test_total_supply (gas: ~1839) +[PASS] testing::ERC20Token::test::test_decimal_is_correct (gas: ~3065) +[PASS] testing::ERC20Token::test::test_approve (gas: ~3165) +[PASS] testing::ERC20Token::test::test_decrease_allowance (gas: ~1015) +[PASS] testing::ERC20Token::test::test_constructor (gas: ~3067) +[PASS] testing::ERC20Token::test::test_transfer_from (gas: ~6130) +[PASS] testing::ERC20Token::test::test_transfer_from_should_fail (gas: ~3145) +[PASS] testing::ERC20Token::test::test_allowance (gas: ~5123) +[PASS] testing::ERC20Token::test::test_transfer (gas: ~3065) +[PASS] testing::ERC20Token::test::test_transfer_from_failed_when_not_approved (gas: ~3165) +[PASS] testing::ERC20Token::test::test_address_balance (gas: ~7335) +[PASS] testing::ERC20Token::test::test_increase_allowance(gas: ~3125) +Tests: 12 passed, 0 failed, 0 skipped, 0 ignored, 0 filtered out +``` + +# Fuzz Testing + +Fuzz testing introduces random inputs to the code to identify vulnerabilities, security issues, and unforeseen behaviors. While you can manually provide these inputs, automation is preferable when testing a broad set of values. + +Let discuss Random Fuzz Testing as a type of Fuzz testing: + +## Random Fuzz testing + +To convert a test to a random fuzz test, simply add arguments to the test function. These arguments can then be used in the test body. The test will be run many times against different randomly generated values. +See the example below in `test_fuzz.cairo`: + +``` +fn sum(a: felt252, b: felt252) -> felt252 { + return a + b; +} + +#[test] +fn test_sum(x: felt252, y: felt252) { + assert(sum(x, y) == x + y, 'sum incorrect'); +} +``` + +Then run `snforge test` + +``` +Running 0 test(s) from tests/ +Tests: 0 passed, 1 failed, 0 skipped, 0 ignored, 0 filtered out + + Running 0 test(s) from src/ Running 1 test(s) from tests/ [PASS] tests::test_fuzz::test_fuzz_sum (fuzzer runs = 256) Tests: 1 passed, 0 failed, 0 skipped - Fuzzer seed: 6375310854403272271 +Fuzzer seed: 214510115079707873 + ``` -The fuzzer supports these types by November 2023: +The fuzzer supports these types by February 2024: - u8 - u16 @@ -438,35 +804,36 @@ The fuzzer supports these types by November 2023: - u256 - felt252 -`Fuzzer Configuration` +# Fuzzer Configuration -You can set the number of runs and the seed for a test: +It is possible to configure the number of runs of the random fuzzer as well as its seed for a specific test case: -```rust - #[test] - #[fuzzer(runs: 100, seed: 38)] - fn test_fuzz_sum(x: felt252, y: felt252) { - assert(mul(x, y) == x * y, 'incorrect'); - } +``` +#[test] +#[fuzzer(runs: 22, seed: 38)] +fn test_sum(x: felt252, y: felt252) { + assert(sum(x, y) == x + y, 'sum incorrect'); +} ``` -Or, use the command line: +It can also be configured globally, via command line arguments: ```shell - $ snforge --fuzzer-runs 500 --fuzzer-seed 4656 +$ snforge test --fuzzer-runs 1234 --fuzzer-seed 1111 ``` Or in `scarb.toml`: ```shell - # ... - [tool.snforge] - fuzzer_runs = 500 - fuzzer_seed = 4656 - # ... +# ... +[tool.snforge] +fuzzer_runs = 1234 +fuzzer_seed = 1111 +# ... + ``` -For more insight on fuzz tests, you can view it [here](https://foundry-rs.github.io/starknet-foundry/testing/fuzz-testing.html#fuzz-testing) +For more insight on fuzz tests, you can view it [here](https://foundry-rs.github.io/starknet-foundry/testing/fuzz-testing.html) ## Filter Tests diff --git a/yarn.lock b/yarn.lock index 4978fb5bf..742b9653e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,442 +3,442 @@ "@babel/runtime@^7.18.9", "@babel/runtime@^7.7.6": - "integrity" "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==" - "resolved" "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz" - "version" "7.23.8" + version "7.23.8" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz" + integrity sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw== dependencies: - "regenerator-runtime" "^0.14.0" + regenerator-runtime "^0.14.0" -"all-contributors-cli@^6.26.1": - "integrity" "sha512-Ymgo3FJACRBEd1eE653FD1J/+uD0kqpUNYfr9zNC1Qby0LgbhDBzB3EF6uvkAbYpycStkk41J+0oo37Lc02yEw==" - "resolved" "https://registry.npmjs.org/all-contributors-cli/-/all-contributors-cli-6.26.1.tgz" - "version" "6.26.1" +all-contributors-cli@^6.26.1: + version "6.26.1" + resolved "https://registry.npmjs.org/all-contributors-cli/-/all-contributors-cli-6.26.1.tgz" + integrity sha512-Ymgo3FJACRBEd1eE653FD1J/+uD0kqpUNYfr9zNC1Qby0LgbhDBzB3EF6uvkAbYpycStkk41J+0oo37Lc02yEw== dependencies: "@babel/runtime" "^7.7.6" - "async" "^3.1.0" - "chalk" "^4.0.0" - "didyoumean" "^1.2.1" - "inquirer" "^7.3.3" - "json-fixer" "^1.6.8" - "lodash" "^4.11.2" - "node-fetch" "^2.6.0" - "pify" "^5.0.0" - "yargs" "^15.0.1" + async "^3.1.0" + chalk "^4.0.0" + didyoumean "^1.2.1" + inquirer "^7.3.3" + json-fixer "^1.6.8" + lodash "^4.11.2" + node-fetch "^2.6.0" + pify "^5.0.0" + yargs "^15.0.1" optionalDependencies: - "prettier" "^2" + prettier "^2" -"ansi-escapes@^4.2.1": - "integrity" "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==" - "resolved" "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" - "version" "4.3.2" +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== dependencies: - "type-fest" "^0.21.3" + type-fest "^0.21.3" -"ansi-regex@^5.0.1": - "integrity" "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - "resolved" "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" - "version" "5.0.1" +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -"ansi-styles@^4.0.0", "ansi-styles@^4.1.0": - "integrity" "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==" - "resolved" "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" - "version" "4.3.0" +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: - "color-convert" "^2.0.1" - -"async@^3.1.0": - "integrity" "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" - "resolved" "https://registry.npmjs.org/async/-/async-3.2.5.tgz" - "version" "3.2.5" - -"camelcase@^5.0.0": - "integrity" "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - "resolved" "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" - "version" "5.3.1" - -"chalk@^4.0.0", "chalk@^4.1.0", "chalk@^4.1.2": - "integrity" "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==" - "resolved" "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" - "version" "4.1.2" + color-convert "^2.0.1" + +async@^3.1.0: + version "3.2.5" + resolved "https://registry.npmjs.org/async/-/async-3.2.5.tgz" + integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== + +camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: - "ansi-styles" "^4.1.0" - "supports-color" "^7.1.0" - -"chardet@^0.7.0": - "integrity" "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" - "resolved" "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz" - "version" "0.7.0" - -"cli-cursor@^3.1.0": - "integrity" "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==" - "resolved" "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz" - "version" "3.1.0" + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== dependencies: - "restore-cursor" "^3.1.0" + restore-cursor "^3.1.0" -"cli-width@^3.0.0": - "integrity" "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" - "resolved" "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz" - "version" "3.0.0" +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== -"cliui@^6.0.0": - "integrity" "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==" - "resolved" "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz" - "version" "6.0.0" +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== dependencies: - "string-width" "^4.2.0" - "strip-ansi" "^6.0.0" - "wrap-ansi" "^6.2.0" - -"color-convert@^2.0.1": - "integrity" "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==" - "resolved" "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" - "version" "2.0.1" + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: - "color-name" "~1.1.4" - -"color-name@~1.1.4": - "integrity" "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - "resolved" "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - "version" "1.1.4" - -"decamelize@^1.2.0": - "integrity" "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" - "resolved" "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" - "version" "1.2.0" - -"didyoumean@^1.2.1": - "integrity" "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" - "resolved" "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz" - "version" "1.2.2" - -"emoji-regex@^8.0.0": - "integrity" "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - "resolved" "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" - "version" "8.0.0" - -"escape-string-regexp@^1.0.5": - "integrity" "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - "resolved" "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - "version" "1.0.5" - -"external-editor@^3.0.3": - "integrity" "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==" - "resolved" "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz" - "version" "3.1.0" + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +decamelize@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + +didyoumean@^1.2.1: + version "1.2.2" + resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== dependencies: - "chardet" "^0.7.0" - "iconv-lite" "^0.4.24" - "tmp" "^0.0.33" - -"figures@^3.0.0": - "integrity" "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==" - "resolved" "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz" - "version" "3.2.0" + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== dependencies: - "escape-string-regexp" "^1.0.5" + escape-string-regexp "^1.0.5" -"find-up@^4.1.0": - "integrity" "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==" - "resolved" "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" - "version" "4.1.0" +find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== dependencies: - "locate-path" "^5.0.0" - "path-exists" "^4.0.0" - -"get-caller-file@^2.0.1": - "integrity" "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - "resolved" "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" - "version" "2.0.5" - -"has-flag@^4.0.0": - "integrity" "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - "resolved" "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" - "version" "4.0.0" - -"husky@^8.0.3": - "integrity" "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==" - "resolved" "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz" - "version" "8.0.3" - -"iconv-lite@^0.4.24": - "integrity" "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==" - "resolved" "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" - "version" "0.4.24" + locate-path "^5.0.0" + path-exists "^4.0.0" + +get-caller-file@^2.0.1: + version "2.0.5" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +husky@^8.0.3: + version "8.0.3" + resolved "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz" + integrity sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg== + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: - "safer-buffer" ">= 2.1.2 < 3" + safer-buffer ">= 2.1.2 < 3" -"inquirer@^7.3.3": - "integrity" "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==" - "resolved" "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz" - "version" "7.3.3" +inquirer@^7.3.3: + version "7.3.3" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== dependencies: - "ansi-escapes" "^4.2.1" - "chalk" "^4.1.0" - "cli-cursor" "^3.1.0" - "cli-width" "^3.0.0" - "external-editor" "^3.0.3" - "figures" "^3.0.0" - "lodash" "^4.17.19" - "mute-stream" "0.0.8" - "run-async" "^2.4.0" - "rxjs" "^6.6.0" - "string-width" "^4.1.0" - "strip-ansi" "^6.0.0" - "through" "^2.3.6" - -"is-fullwidth-code-point@^3.0.0": - "integrity" "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - "resolved" "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" - "version" "3.0.0" - -"json-fixer@^1.6.8": - "integrity" "sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw==" - "resolved" "https://registry.npmjs.org/json-fixer/-/json-fixer-1.6.15.tgz" - "version" "1.6.15" + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +json-fixer@^1.6.8: + version "1.6.15" + resolved "https://registry.npmjs.org/json-fixer/-/json-fixer-1.6.15.tgz" + integrity sha512-TuDuZ5KrgyjoCIppdPXBMqiGfota55+odM+j2cQ5rt/XKyKmqGB3Whz1F8SN8+60yYGy/Nu5lbRZ+rx8kBIvBw== dependencies: "@babel/runtime" "^7.18.9" - "chalk" "^4.1.2" - "pegjs" "^0.10.0" + chalk "^4.1.2" + pegjs "^0.10.0" -"locate-path@^5.0.0": - "integrity" "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==" - "resolved" "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" - "version" "5.0.0" +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== dependencies: - "p-locate" "^4.1.0" - -"lodash@^4.11.2", "lodash@^4.17.19": - "integrity" "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - "resolved" "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" - "version" "4.17.21" - -"mimic-fn@^2.1.0": - "integrity" "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" - "resolved" "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" - "version" "2.1.0" - -"mute-stream@0.0.8": - "integrity" "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" - "resolved" "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz" - "version" "0.0.8" - -"node-fetch@^2.6.0": - "integrity" "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==" - "resolved" "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" - "version" "2.7.0" + p-locate "^4.1.0" + +lodash@^4.11.2, lodash@^4.17.19: + version "4.17.21" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +node-fetch@^2.6.0: + version "2.7.0" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: - "whatwg-url" "^5.0.0" + whatwg-url "^5.0.0" -"onetime@^5.1.0": - "integrity" "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==" - "resolved" "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" - "version" "5.1.2" +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== dependencies: - "mimic-fn" "^2.1.0" + mimic-fn "^2.1.0" -"os-tmpdir@~1.0.2": - "integrity" "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" - "resolved" "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" - "version" "1.0.2" +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== -"p-limit@^2.2.0": - "integrity" "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==" - "resolved" "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" - "version" "2.3.0" +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== dependencies: - "p-try" "^2.0.0" + p-try "^2.0.0" -"p-locate@^4.1.0": - "integrity" "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==" - "resolved" "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" - "version" "4.1.0" +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== dependencies: - "p-limit" "^2.2.0" - -"p-try@^2.0.0": - "integrity" "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - "resolved" "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" - "version" "2.2.0" - -"path-exists@^4.0.0": - "integrity" "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - "resolved" "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" - "version" "4.0.0" - -"pegjs@^0.10.0": - "integrity" "sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==" - "resolved" "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz" - "version" "0.10.0" - -"pify@^5.0.0": - "integrity" "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==" - "resolved" "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz" - "version" "5.0.0" - -"prettier@^2": - "integrity" "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==" - "resolved" "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz" - "version" "2.8.8" - -"prettier@^3.2.4": - "integrity" "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==" - "resolved" "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz" - "version" "3.2.4" - -"regenerator-runtime@^0.14.0": - "integrity" "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" - "resolved" "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz" - "version" "0.14.1" - -"require-directory@^2.1.1": - "integrity" "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" - "resolved" "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" - "version" "2.1.1" - -"require-main-filename@^2.0.0": - "integrity" "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - "resolved" "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz" - "version" "2.0.0" - -"restore-cursor@^3.1.0": - "integrity" "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==" - "resolved" "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz" - "version" "3.1.0" + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +pegjs@^0.10.0: + version "0.10.0" + resolved "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz" + integrity sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow== + +pify@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz" + integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== + +prettier@^2: + version "2.8.8" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== + +prettier@^3.2.4: + version "3.2.4" + resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz" + integrity sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ== + +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-main-filename@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz" + integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== dependencies: - "onetime" "^5.1.0" - "signal-exit" "^3.0.2" - -"run-async@^2.4.0": - "integrity" "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" - "resolved" "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" - "version" "2.4.1" - -"rxjs@^6.6.0": - "integrity" "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==" - "resolved" "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" - "version" "6.6.7" + onetime "^5.1.0" + signal-exit "^3.0.2" + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +rxjs@^6.6.0: + version "6.6.7" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== dependencies: - "tslib" "^1.9.0" + tslib "^1.9.0" "safer-buffer@>= 2.1.2 < 3": - "integrity" "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - "resolved" "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" - "version" "2.1.2" - -"set-blocking@^2.0.0": - "integrity" "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" - "resolved" "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" - "version" "2.0.0" - -"signal-exit@^3.0.2": - "integrity" "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - "resolved" "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" - "version" "3.0.7" - -"string-width@^4.1.0", "string-width@^4.2.0": - "integrity" "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==" - "resolved" "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - "version" "4.2.3" + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +signal-exit@^3.0.2: + version "3.0.7" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== dependencies: - "emoji-regex" "^8.0.0" - "is-fullwidth-code-point" "^3.0.0" - "strip-ansi" "^6.0.1" - -"strip-ansi@^6.0.0", "strip-ansi@^6.0.1": - "integrity" "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==" - "resolved" "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - "version" "6.0.1" + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: - "ansi-regex" "^5.0.1" + ansi-regex "^5.0.1" -"supports-color@^7.1.0": - "integrity" "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==" - "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" - "version" "7.2.0" +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: - "has-flag" "^4.0.0" + has-flag "^4.0.0" -"through@^2.3.6": - "integrity" "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" - "resolved" "https://registry.npmjs.org/through/-/through-2.3.8.tgz" - "version" "2.3.8" +through@^2.3.6: + version "2.3.8" + resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== -"tmp@^0.0.33": - "integrity" "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==" - "resolved" "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz" - "version" "0.0.33" +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== dependencies: - "os-tmpdir" "~1.0.2" - -"tr46@~0.0.3": - "integrity" "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - "resolved" "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" - "version" "0.0.3" - -"tslib@^1.9.0": - "integrity" "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - "resolved" "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" - "version" "1.14.1" - -"type-fest@^0.21.3": - "integrity" "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" - "resolved" "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" - "version" "0.21.3" - -"webidl-conversions@^3.0.0": - "integrity" "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - "resolved" "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" - "version" "3.0.1" - -"whatwg-url@^5.0.0": - "integrity" "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==" - "resolved" "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" - "version" "5.0.0" + os-tmpdir "~1.0.2" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +tslib@^1.9.0: + version "1.14.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== dependencies: - "tr46" "~0.0.3" - "webidl-conversions" "^3.0.0" - -"which-module@^2.0.0": - "integrity" "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" - "resolved" "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz" - "version" "2.0.1" - -"wrap-ansi@^6.2.0": - "integrity" "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==" - "resolved" "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz" - "version" "6.2.0" + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which-module@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz" + integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== + +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== dependencies: - "ansi-styles" "^4.0.0" - "string-width" "^4.1.0" - "strip-ansi" "^6.0.0" - -"y18n@^4.0.0": - "integrity" "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" - "resolved" "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz" - "version" "4.0.3" - -"yargs-parser@^18.1.2": - "integrity" "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==" - "resolved" "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz" - "version" "18.1.3" + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + +yargs-parser@^18.1.2: + version "18.1.3" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz" + integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== dependencies: - "camelcase" "^5.0.0" - "decamelize" "^1.2.0" + camelcase "^5.0.0" + decamelize "^1.2.0" -"yargs@^15.0.1": - "integrity" "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==" - "resolved" "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz" - "version" "15.4.1" +yargs@^15.0.1: + version "15.4.1" + resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== dependencies: - "cliui" "^6.0.0" - "decamelize" "^1.2.0" - "find-up" "^4.1.0" - "get-caller-file" "^2.0.1" - "require-directory" "^2.1.1" - "require-main-filename" "^2.0.0" - "set-blocking" "^2.0.0" - "string-width" "^4.2.0" - "which-module" "^2.0.0" - "y18n" "^4.0.0" - "yargs-parser" "^18.1.2" + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2"