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 tests for Utils package #1292

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
18 changes: 15 additions & 3 deletions packages/test_common/src/mocks/checkpoint.cairo
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
use openzeppelin_utils::structs::Checkpoint;

#[starknet::interface]
pub trait IMockTrace<TContractState> {
fn push_checkpoint(ref self: TContractState, key: u64, value: u256) -> (u256, u256);
fn get_latest(self: @TContractState) -> u256;
fn get_at_key(self: @TContractState, key: u64) -> u256;
fn get_at_position(self: @TContractState, pos: u64) -> Checkpoint;
fn upper_lookup(self: @TContractState, key: u64) -> u256;
fn upper_lookup_recent(self: @TContractState, key: u64) -> u256;
fn get_length(self: @TContractState) -> u64;
}

#[starknet::contract]
pub mod MockTrace {
use openzeppelin_utils::structs::checkpoint::{Trace, TraceTrait};
use openzeppelin_utils::structs::checkpoint::{Checkpoint, Trace, TraceTrait};

#[storage]
struct Storage {
Expand All @@ -25,10 +29,18 @@ pub mod MockTrace {
self.trace.deref().latest()
}

fn get_at_key(self: @ContractState, key: u64) -> u256 {
fn get_at_position(self: @ContractState, pos: u64) -> Checkpoint {
self.trace.deref().at(pos)
}

fn upper_lookup(self: @ContractState, key: u64) -> u256 {
self.trace.deref().upper_lookup(key)
}

fn upper_lookup_recent(self: @ContractState, key: u64) -> u256 {
self.trace.deref().upper_lookup_recent(key)
}

fn get_length(self: @ContractState) -> u64 {
self.trace.deref().length()
}
Expand Down
2 changes: 1 addition & 1 deletion packages/utils/src/structs/checkpoint.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub struct Trace {
}

/// Generic checkpoint representation.
#[derive(Copy, Drop, Serde)]
#[derive(Copy, Drop, Serde, PartialEq)]
pub struct Checkpoint {
pub key: u64,
pub value: u256,
Expand Down
5 changes: 4 additions & 1 deletion packages/utils/src/tests.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
mod test_checkpoint;

#[cfg(feature: 'fuzzing')]
mod test_fuzz_checkpoint;
#[cfg(feature: 'fuzzing')]
mod test_fuzz_deployments;
#[cfg(feature: 'fuzzing')]
mod test_fuzz_math;
mod test_nonces;
Expand Down
30 changes: 24 additions & 6 deletions packages/utils/src/tests/test_checkpoint.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use core::num::traits::Bounded;
use crate::structs::checkpoint::Checkpoint;
use crate::structs::Checkpoint;
use openzeppelin_test_common::mocks::checkpoint::{IMockTrace, MockTrace};
use starknet::storage_access::StorePacking;

Expand Down Expand Up @@ -36,20 +36,38 @@ fn test_get_latest() {
}

#[test]
fn test_get_at_key() {
fn test_upper_lookup() {
let mut mock_trace = CONTRACT_STATE();

mock_trace.push_checkpoint(100, 1000);
mock_trace.push_checkpoint(200, 2000);
mock_trace.push_checkpoint(300, 3000);

let value_at_150 = mock_trace.get_at_key(150);
let value_at_150 = mock_trace.upper_lookup(150);
assert_eq!(value_at_150, 1000);

let value_at_250 = mock_trace.get_at_key(250);
let value_at_250 = mock_trace.upper_lookup(250);
assert_eq!(value_at_250, 2000);

let value_at_350 = mock_trace.get_at_key(350);
let value_at_350 = mock_trace.upper_lookup(350);
assert_eq!(value_at_350, 3000);
}

#[test]
fn test_upper_lookup_recent() {
let mut mock_trace = CONTRACT_STATE();

mock_trace.push_checkpoint(100, 1000);
mock_trace.push_checkpoint(200, 2000);
mock_trace.push_checkpoint(300, 3000);

let value_at_150 = mock_trace.upper_lookup_recent(150);
assert_eq!(value_at_150, 1000);

let value_at_250 = mock_trace.upper_lookup_recent(250);
assert_eq!(value_at_250, 2000);

let value_at_350 = mock_trace.upper_lookup_recent(350);
assert_eq!(value_at_350, 3000);
}

Expand All @@ -67,7 +85,7 @@ fn test_get_length() {
}

#[test]
#[should_panic(expected: ('Unordered insertion',))]
#[should_panic(expected: 'Unordered insertion')]
fn test_unordered_insertion() {
let mut mock_trace = CONTRACT_STATE();

Expand Down
122 changes: 122 additions & 0 deletions packages/utils/src/tests/test_fuzz_checkpoint.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use crate::structs::Checkpoint;
use openzeppelin_test_common::mocks::checkpoint::{IMockTrace, MockTrace};
use starknet::storage_access::StorePacking;

fn CONTRACT_STATE() -> MockTrace::ContractState {
MockTrace::contract_state_for_testing()
}

#[test]
fn test_push_multiple(len_seed: u64, key_step_seed: u64) {
let len = 2 + len_seed % 99; // [2..100]
let key_step = 1 + key_step_seed % 1_000_000; // [1..1_000_000]

let mut mock_trace = CONTRACT_STATE();
let checkpoints = build_checkpoints_array(len, key_step);

let mut expected_prev = 0;
for point in checkpoints {
let (prev, new) = mock_trace.push_checkpoint(*point.key, *point.value);
assert_eq!(prev, expected_prev);
assert_eq!(new, *point.value);
expected_prev = new;
};
assert_eq!(mock_trace.get_length(), len);
}

#[test]
fn test_upper_lookup(len_seed: u64, key_step_seed: u64) {
let len = 2 + len_seed % 99; // [2..100]
let key_step = 1 + key_step_seed % 1_000_000; // [1..1_000_000]

let mut mock_trace = CONTRACT_STATE();
let checkpoints = build_checkpoints_array(len, key_step);
push_checkpoints(checkpoints);

for i in 0..len {
let index = i.try_into().unwrap();
let checkpoint = *checkpoints.at(index);
let found_value = mock_trace.upper_lookup(checkpoint.key);
assert_eq!(found_value, checkpoint.value);
};
}

#[test]
fn test_upper_lookup_recent(len_seed: u64, key_step_seed: u64) {
let len = 2 + len_seed % 99; // [2..100]
let key_step = 1 + key_step_seed % 1_000_000; // [1..1_000_000]

let mut mock_trace = CONTRACT_STATE();
let checkpoints = build_checkpoints_array(len, key_step);
push_checkpoints(checkpoints);

for i in 0..len {
let index = i.try_into().unwrap();
let checkpoint = *checkpoints.at(index);
let found_value = mock_trace.upper_lookup_recent(checkpoint.key);
assert_eq!(found_value, checkpoint.value);
};
}

#[test]
fn test_get_at_position(len_seed: u64, key_step_seed: u64) {
let len = 2 + len_seed % 99; // [2..100]
let key_step = 1 + key_step_seed % 1_000_000; // [1..1_000_000]

let mut mock_trace = CONTRACT_STATE();
let checkpoints = build_checkpoints_array(len, key_step);
push_checkpoints(checkpoints);

for i in 0..len {
let index = i.try_into().unwrap();
let checkpoint = *checkpoints.at(index);
let found_checkpoint = mock_trace.get_at_position(i);
assert!(found_checkpoint == checkpoint);
};
}

#[test]
#[should_panic(expected: 'Vec overflow')]
fn test_at_position_out_of_bounds(len_seed: u64, key_step_seed: u64) {
let len = 2 + len_seed % 99; // [2..100]
let key_step = 1 + key_step_seed % 1_000_000; // [1..1_000_000]

let mut mock_trace = CONTRACT_STATE();
let checkpoints = build_checkpoints_array(len, key_step);
push_checkpoints(checkpoints);

mock_trace.get_at_position(len);
}

#[test]
fn test_pack_unpack(key: u64, value: u256) {
let initial_checkpoint = Checkpoint { key, value };

let packed_value = StorePacking::pack(initial_checkpoint);
let unpacked_checkpoint: Checkpoint = StorePacking::unpack(packed_value);

assert!(initial_checkpoint == unpacked_checkpoint);
}

//
// Helpers
//

fn build_checkpoints_array(len: u64, key_step: u64) -> Span<Checkpoint> {
let mut checkpoints = array![];
for i in 0..len {
// Keys are guaranteed to be positive and increase by `key_step`
let key = 1 + key_step * i;
// Values are guaranteed to be positive, different and increase by 1
let value = (1 + i).into();
checkpoints.append(Checkpoint { key, value });
};
checkpoints.span()
}

fn push_checkpoints(checkpoints: Span<Checkpoint>) {
let mut mock_trace = CONTRACT_STATE();
for point in checkpoints {
mock_trace.push_checkpoint(*point.key, *point.value);
};
}
23 changes: 23 additions & 0 deletions packages/utils/src/tests/test_fuzz_deployments.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use crate::deployments::calculate_contract_address_from_deploy_syscall;
use snforge_std::{ContractClass, ContractClassTrait, test_address};
use starknet::ClassHash;

#[test]
fn test_compute_contract_address(
class_hash: felt252, arg_1: felt252, arg_2: felt252, arg_3: felt252,
) {
let class_hash: ClassHash = match class_hash.try_into() {
Option::Some(class_hash) => class_hash,
Option::None => { return; },
};
let deployer_address = test_address();
let contract_class = ContractClass { class_hash };
let constructor_calldata = array![arg_1, arg_2, arg_3];
let salt = 0;
let expected_address = contract_class.precalculate_address(@constructor_calldata);

let computed_address = calculate_contract_address_from_deploy_syscall(
salt, class_hash, constructor_calldata.span(), deployer_address,
);
assert_eq!(computed_address, expected_address);
}
Loading