Skip to content
This repository has been archived by the owner on Jun 20, 2024. It is now read-only.

Commit

Permalink
snforge chapter updated
Browse files Browse the repository at this point in the history
  • Loading branch information
Akinbola247 committed Oct 20, 2023
1 parent baeba7d commit 8bc3203
Showing 1 changed file with 47 additions and 15 deletions.
62 changes: 47 additions & 15 deletions src/ch02-11-foundry-forge.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ tree . -L 1

The project structure is as follows:


```shell
.
├── README.md
Expand Down Expand Up @@ -114,7 +113,6 @@ Running 3 test(s) from `src/`
Tests: 3 passed, 0 failed, 0 skipped
```
## Basic Example
The example provided below demonstrates how to test a Starknet contract using `snforge`.
Expand Down Expand Up @@ -191,16 +189,18 @@ Tests: 1 passed, 0 failed, 0 skipped
## Diving deep into Smart contract testing with `snforge` command line.
Ideally, there are various way to test your smart contract which may include unit and integration test, fuzz, fork, E2E test, and testing with foundry cheatcodes. In this section, we'll be considering an ERC20 example contract from starknet-js examples, And we'll be taking into consideration unit and integration tests, filtering, foundry cheatcodes and fuzz testing using `snforge` cli.
Ideally, there are various way to test your smart contract which may include unit and integration test, fuzz, fork, E2E test, and testing with foundry cheatcodes. In this section, we'll be considering an ERC20 example contract from starknet-js examples, And we'll be taking into consideration unit and integration tests, filtering, foundry cheatcodes and fuzz testing using `snforge` cli.
## An ERC20 example
After initializing your foundry project, include the below in your `Scarb.toml` in your dependencies:
```shell
openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.7.0" }
```
Now, Take a look at the erc20 smart contract below:
```rust
use starknet::ContractAddress;
#[starknet::interface]
Expand Down Expand Up @@ -245,11 +245,13 @@ mod erc20 {
}
}
```
It's a basic erc20 contract that allows us to `mint` token to recipient at deployment, check the `balance of` addresses and `transfer` tokens. We are relying on openzeppelin ERC20 library.
## Test setup
Structure the test file as below and bring in necessary imports as below;
```rust
#[cfg(test)]
mod tests {
Expand All @@ -265,7 +267,9 @@ Structure the test file as below and bring in necessary imports as below;
//rest of the code goes here.
}
```
When writing test cases we need an helper function to deploy an instance of the contract. The helper function will take in two arguments i.e the `supply` amount and the `recipient` address as below;
```rust
// ...
use snforge_std::{declare, ContractClassTrait};
Expand All @@ -280,27 +284,31 @@ When writing test cases we need an helper function to deploy an instance of the
}
//...rest for the code
```
We then import the `declare` and `ContractClassTrait` from `snforge_std`, after which we initialize the arguments(`supply`,`recipient`), declare the contract, compute the calldata and deploy.
## Writing the Test cases
Firstly, we need to test deployment helper function and the balance of the recipient to confirm mint value as below;
```rust
// ...
use erc20_contract::erc20::Ierc20SafeDispatcher;
use erc20_contract::erc20::Ierc20SafeDispatcherTrait;

#[test]
#[available_gas(3000000000000000)]
fn test_balance_of() {
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');
}
```
Run `snforge` you should get this output:
```shell
snforge

Expand All @@ -324,11 +332,11 @@ What are cheat codes? they are basically helper functions exposed by `snforge_st
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;
//sets the caller of the function
start_prank(contract_address, sender);
safe_dispatcher.transfer(receiver.into(), amount.into());
Expand All @@ -341,7 +349,9 @@ What are cheat codes? they are basically helper functions exposed by `snforge_st
}
```
Run `snforge`, on the two test cases above, you should get an output like this:
```shell
snforge
Expand All @@ -352,6 +362,7 @@ Run `snforge`, on the two test cases above, you should get an output like this:
[PASS] tests::test_erc20::tests::test_transfer
Tests: 2 passed, 0 failed, 0 skipped
```
`start_prank` in the above snippet, sets the caller of the transfer function and the `stop_prank` cancels the `start_prank` at the end of the function call.
<details>
Expand Down Expand Up @@ -383,7 +394,7 @@ Run `snforge`, on the two test cases above, you should get an output like this:
#[test]
#[available_gas(3000000000000000)]
fn test_balance_of() {
fn test_balance_of() {
let contract_address = deploy_contract('erc20');
let safe_dispatcher = Ierc20SafeDispatcher { contract_address };
let reciepient = starknet::contract_address_const::<0x01>();
Expand All @@ -396,11 +407,11 @@ Run `snforge`, on the two test cases above, you should get an output like this:
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();
Expand All @@ -409,10 +420,10 @@ Run `snforge`, on the two test cases above, you should get an output like this:
}
}
</details>
</details>
## Fuzz testing
## Fuzz testing
What's fuzz testing? Fuzz testing for involves subjecting the code to random inputs and scenarios to discover vulnerabilities, security flaws, and unexpected behavior. Although you can decide to input these random values yourself, nonetheless, its not ideal most especially when you intend to test a wide range of possible values. Consider the below snippet in a test_fuzz.cairo file:
Expand All @@ -426,7 +437,9 @@ What's fuzz testing? Fuzz testing for involves subjecting the code to random inp
assert(mul(x, y) == x * y, 'incorrect');
}
```
The output after running `snforge` should look like this:
```shell
snforge
Expand All @@ -437,7 +450,9 @@ The output after running `snforge` should look like this:
Tests: 1 passed, 0 failed, 0 skipped
Fuzzer seed: 6375310854403272271
```
At the moment, the fuzzer only supports generating values for the following types :
- u8
- u16
- u32
Expand All @@ -449,18 +464,23 @@ At the moment, the fuzzer only supports generating values for the following type
## Configuring fuzzer
It is possible to configure the number of runs and also specified the seed for a test as below:
```rust
#[test]
#[fuzzer(runs: 100, seed: 38)]
fn test_fuzz_sum (x: felt252, y: felt252){
assert(mul(x, y) == x * y, 'incorrect');
}
```
or configure using the command line :
```shell
$ snforge --fuzzer-runs 500 --fuzzer-seed 4656
```
or in scarb.toml:
```shell
# ...
[tool.snforge]
Expand All @@ -471,11 +491,14 @@ or in scarb.toml:
## Filter tests
Run specific tests using a filter string after the `snforge` command. Tests matching the filter based on their absolute module tree path will be executed:. for example; with reference to the above test cases, if we run this
Run specific tests using a filter string after the `snforge` command. Tests matching the filter based on their absolute module tree path will be executed:. for example; with reference to the above test cases, if we run this
```rust,ignore
$ snforge test_
```
we should get this output:
```shell
Collected 3 test(s) from erc20_contract package
Running 0 test(s) from src/
Expand All @@ -486,12 +509,16 @@ we should get this output:
Tests: 3 passed, 0 failed, 0 skipped
Fuzzer seed: 10426315620495146768
```
When you look closely, all the tests with the string 'test_' in their test name when through. Now, consider this second example;
When you look closely, all the tests with the string 'test\_' in their test name when through. Now, consider this second example;
if we run this:
```shell
$ snforge fuzz_sum
```
The output should look like this:
```shell
Collected 1 test(s) from erc20_contract package
Expand All @@ -501,21 +528,27 @@ The output should look like this:
Tests: 1 passed, 0 failed, 0 skipped
Fuzzer seed: 12607758074950729376
```
What do you notice? Yes, that's right, only the `test_fuzz_sum` went through because we filtered which test to run with the string 'fuzz_sum'.
What do you notice? Yes, that's right, only the `test_fuzz_sum` went through because we filtered which test to run with the string 'fuzz_sum'.
## Run a Specific Test
Use the `--exact` flag and a fully qualified test name to run a particular test. In order to run a specifc test with the --exact flag, run the following command:
```shell
$ snforge package_name::<test_name> --exact
```
## Stop After First Test Failure
To stop test execution after the first failed test, include the `--exit-first` flag with the `snforge` command like this:
```shell
$ snforge --exit-first
```
Peradventure there's a failing test, the output should look like this:
```shell
Collected 3 test(s) from erc20_contract package
Running 0 test(s) from src/
Expand All @@ -532,4 +565,3 @@ Peradventure there's a failing test, the output should look like this:
Failures:
tests::test_erc20::tests::test_balance_of
```

0 comments on commit 8bc3203

Please sign in to comment.