Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fuzz testing #1255

Merged
merged 17 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
run: scarb fmt --check --workspace

- name: Run tests
run: snforge test --workspace
run: snforge test --workspace --features fuzzing --fuzzer-runs 500
immrsd marked this conversation as resolved.
Show resolved Hide resolved

# Issue with cairo-coverage. Re-add to CI once issues are fixed.
#
Expand Down
1 change: 1 addition & 0 deletions packages/test_common/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod erc1155;
pub mod erc20;
pub mod erc721;
pub mod eth_account;
pub mod math;
pub mod mocks;
pub mod ownable;
pub mod upgrades;
Expand Down
16 changes: 16 additions & 0 deletions packages/test_common/src/math.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use core::num::traits::ops::overflowing::{OverflowingAdd, OverflowingMul, OverflowingSub};

pub fn is_overflow_add<T, +OverflowingAdd<T>, +Drop<T>>(x: T, y: T) -> bool {
let (_, does_overflow) = x.overflowing_add(y);
does_overflow
}

pub fn is_overflow_mul<T, +OverflowingMul<T>, +Drop<T>>(x: T, y: T) -> bool {
let (_, does_overflow) = x.overflowing_mul(y);
does_overflow
}

pub fn is_overflow_sub<T, +OverflowingSub<T>, +Drop<T>>(x: T, y: T) -> bool {
let (_, does_overflow) = x.overflowing_sub(y);
does_overflow
}
3 changes: 3 additions & 0 deletions packages/token/Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ snforge_std.workspace = true
openzeppelin_testing = { path = "../testing" }
openzeppelin_test_common = { path = "../test_common" }

[features]
fuzzing = []

[lib]

[[target.starknet-contract]]
Expand Down
3 changes: 3 additions & 0 deletions packages/token/src/tests/erc20.cairo
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
mod test_erc20;
mod test_erc20_permit;

#[cfg(feature: 'fuzzing')]
mod test_fuzz_erc20;
165 changes: 165 additions & 0 deletions packages/token/src/tests/erc20/test_fuzz_erc20.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
use ERC20Component::InternalTrait;
use core::num::traits::Bounded;
use crate::erc20::ERC20Component;
use crate::erc20::ERC20Component::{ERC20CamelOnlyImpl, ERC20Impl};
use crate::erc20::ERC20Component::{ERC20MetadataImpl, InternalImpl};
use openzeppelin_test_common::math::{is_overflow_add, is_overflow_sub};
use openzeppelin_test_common::mocks::erc20::DualCaseERC20Mock;
use openzeppelin_testing::constants::{NAME, OWNER, RECIPIENT, SPENDER, SYMBOL};
use snforge_std::{start_cheat_caller_address, test_address};
use starknet::ContractAddress;

//
// Setup
//

type ComponentState = ERC20Component::ComponentState<DualCaseERC20Mock::ContractState>;

fn COMPONENT_STATE() -> ComponentState {
ERC20Component::component_state_for_testing()
}

fn setup(supply: u256) -> ComponentState {
let mut state = COMPONENT_STATE();
state.initializer(NAME(), SYMBOL());
state.mint(OWNER(), supply);
state
}

//
// Tests
//

#[test]
fn test_mint(supply: u256, mint_amount: u256) {
if is_overflow_add(supply, mint_amount) {
return;
}
let mut state = setup(supply);

assert_total_supply(supply);
assert_balance(OWNER(), supply);

state.mint(RECIPIENT(), mint_amount);
assert_total_supply(supply + mint_amount);
assert_balance(RECIPIENT(), mint_amount);
}

#[test]
fn test_burn(supply: u256, burn_amount: u256) {
if is_overflow_sub(supply, burn_amount) {
return;
}
let mut state = setup(supply);

assert_total_supply(supply);
assert_balance(OWNER(), supply);

state.burn(OWNER(), burn_amount);
assert_total_supply(supply - burn_amount);
assert_balance(OWNER(), supply - burn_amount);
}

#[test]
fn test_mint_burn(initial_supply: u256, mint_amount: u256, burn_amount: u256) {
if is_overflow_add(initial_supply, mint_amount) {
return;
}
if is_overflow_sub(mint_amount, burn_amount) {
return;
}
let mut state = setup(initial_supply);
let (owner, recipient) = (OWNER(), RECIPIENT());

// Mint
state.mint(recipient, mint_amount);
assert_total_supply(initial_supply + mint_amount);
assert_balance(owner, initial_supply);
assert_balance(recipient, mint_amount);

// Burn
state.burn(recipient, burn_amount);
assert_total_supply(initial_supply + mint_amount - burn_amount);
assert_balance(owner, initial_supply);
assert_balance(recipient, mint_amount - burn_amount);
}

#[test]
fn test_transfer(supply: u256, transfer_amount: u256) {
if is_overflow_sub(supply, transfer_amount) {
return;
}
let mut state = setup(supply);
let (owner, recipient) = (OWNER(), RECIPIENT());

start_cheat_caller_address(test_address(), owner);
state.transfer(recipient, transfer_amount);

assert_balance(owner, supply - transfer_amount);
assert_balance(recipient, transfer_amount);
}

#[test]
fn test_transfer_from(supply: u256, transfer_amount: u256) {
if is_overflow_sub(supply, transfer_amount) {
return;
}
let mut state = setup(supply);
let (owner, spender, recipient) = (OWNER(), SPENDER(), RECIPIENT());
let contract_address = test_address();

// Approve
start_cheat_caller_address(contract_address, owner);
state.approve(spender, transfer_amount);
assert_balance(owner, supply);
assert_allowance(owner, spender, transfer_amount);

// Transfer from
start_cheat_caller_address(contract_address, spender);
state.transfer_from(owner, recipient, transfer_amount);
assert_allowance(owner, spender, 0);
assert_balance(owner, supply - transfer_amount);
assert_balance(recipient, transfer_amount);
assert_balance(spender, 0);
}

#[test]
fn test__spend_allowance(supply: u256, spend_amount: u256) {
if is_overflow_sub(supply, spend_amount) {
return;
}
let mut state = setup(supply);
let (owner, spender) = (OWNER(), SPENDER());
state._approve(owner, spender, supply);

state._spend_allowance(owner, spender, spend_amount);

// Allowance doesn't change if it's set to maximum
let expected_allowance = if supply == Bounded::MAX {
supply
} else {
supply - spend_amount
};
assert_balance(owner, supply);
assert_balance(spender, 0);
assert_allowance(owner, spender, expected_allowance);
}

//
// Helpers
//

fn assert_total_supply(expected: u256) {
let state = COMPONENT_STATE();
assert_eq!(state.total_supply(), expected);
}

fn assert_allowance(owner: ContractAddress, spender: ContractAddress, expected: u256) {
let state = COMPONENT_STATE();
assert_eq!(state.allowance(owner, spender), expected);
}

fn assert_balance(owner: ContractAddress, expected: u256) {
let state = COMPONENT_STATE();
assert_eq!(state.balance_of(owner), expected);
}
3 changes: 3 additions & 0 deletions packages/token/src/tests/erc721.cairo
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
mod test_erc721;
mod test_erc721_enumerable;
mod test_erc721_receiver;

#[cfg(feature: 'fuzzing')]
mod test_fuzz_erc721_enumerable;
Loading
Loading