Skip to content

Commit

Permalink
unit test & payable function detection
Browse files Browse the repository at this point in the history
  • Loading branch information
kevin-valerio committed Sep 24, 2024
1 parent e455b16 commit 2557f9d
Show file tree
Hide file tree
Showing 11 changed files with 129 additions and 35 deletions.
Binary file modified phink/corpus/selector_2.bin
Binary file not shown.
Binary file added phink/corpus/selector_6.bin
Binary file not shown.
1 change: 1 addition & 0 deletions phink/selectors.dict
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
delimiter="********"
"9bae9d5e"
"fa80c2f6"
"27d8f137"
2 changes: 1 addition & 1 deletion src/cli/ziggy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ mod tests {
config.config.fuzz_output,
Some(PathBuf::from("/tmp/fuzz_output"))
);
assert_eq!(config.contract_path, PathBuf::from("/path/to/contract"));
assert_eq!(config.contract_path, PathBuf::from("sample/dummy"));
}

#[test]
Expand Down
30 changes: 30 additions & 0 deletions src/contract/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,26 @@ impl PayloadCrafter {
Ok(all_selectors)
}

pub fn extract_payables(json_data: &str) -> Option<Vec<Selector>> {
let data: Value = serde_json::from_str(json_data).expect("JSON was not well-formatted");

Some(
data["spec"]["messages"]
.as_array()
.unwrap_or(&Vec::new())
.iter()
.filter_map(|message| {
if message["payable"].as_bool() == Some(true) {
message["selector"]
.as_str()
.map(|s| Selector::try_from(s).unwrap())
} else {
None
}
})
.collect(),
)
}
/// Extract every selector associated to the invariants defined in the ink!
/// smart-contract See the documentation of `DEFAULT_PHINK_PREFIX` to know
/// more about how to create a properties
Expand Down Expand Up @@ -212,6 +232,16 @@ mod test {
assert_eq!(invariants[1], Selector::from([0x23, 0x45, 0x67, 0x89]));
}

#[test]
fn test_extract_payable() {
let specs = fs::read_to_string("sample/transfer/target/ink/transfer.json").unwrap();

let invariants = PayloadCrafter::extract_payables(specs.as_str()).unwrap();

assert_eq!(invariants.len(), 1);
assert_eq!(invariants[0], Selector::from([0x47, 0x18, 0x7f, 0x3e])); // 0x47 18 7f 3e
}

#[test]
fn test_get_constructor() {
let json_data = r#"
Expand Down
8 changes: 8 additions & 0 deletions src/contract/selectors/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use anyhow::bail;
pub struct SelectorDatabase {
invariants: Vec<Selector>,
messages: Vec<Selector>,
payable_messages: Vec<Selector>,
}

impl Default for SelectorDatabase {
Expand All @@ -18,9 +19,16 @@ impl SelectorDatabase {
Self {
invariants: Vec::default(),
messages: Vec::default(),
payable_messages: Vec::default(),
}
}

pub fn is_payable(&self, selector: &Selector) -> bool {
self.payable_messages.contains(selector)
}
pub fn add_payables(&mut self, selectors: Vec<Selector>) {
self.payable_messages.extend(selectors);
}
pub fn exists(&self, selector: Selector) -> bool {
self.messages.contains(&selector) || self.invariants.contains(&selector)
}
Expand Down
75 changes: 75 additions & 0 deletions src/fuzzer/envbuilder.rs → src/fuzzer/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,78 @@ impl EnvironmentBuilder {
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
use std::{
fs,
io,
};
use tempfile::tempdir;

fn create_test_selector() -> Selector {
Selector([0x01, 0x02, 0x03, 0x04])
}

fn create_temp_phink_file(file_name: &str) -> PathBuf {
let dir = tempdir().unwrap();
dir.path().join(file_name)
}

#[test]
fn test_dict_new_creates_file() -> io::Result<()> {
let path = create_temp_phink_file("test_dict");
let phink_file = PhinkFiles::new(path.clone());

let dict = Dict::new(phink_file)?;
assert!(dict.file_path.exists());

let contents = fs::read_to_string(dict.file_path)?;
assert!(contents.contains("# Dictionary file for selectors"));

Ok(())
}

#[test]
fn test_write_dict_entry() -> anyhow::Result<()> {
let path = create_temp_phink_file("test_dict");
let phink_file = PhinkFiles::new(path.clone());
let dict = Dict::new(phink_file)?;
let selector = create_test_selector(); // Selector([0x01, 0x02, 0x03, 0x04])
dict.write_dict_entry(&selector)?;
let contents = fs::read_to_string(dict.file_path)?;
assert!(contents.contains("01020304"));

Ok(())
}

#[test]
fn test_corpus_manager_new_creates_dir() -> anyhow::Result<()> {
let path = create_temp_phink_file("test_corpus");
let phink_file = PhinkFiles::new(path.clone());

let corpus_manager = CorpusManager::new(phink_file)?;
assert!(corpus_manager.corpus_dir.exists());

Ok(())
}

#[test]
fn test_write_corpus_file() -> io::Result<()> {
let path = create_temp_phink_file("test_corpus");
let phink_file = PhinkFiles::new(path.clone());
let corpus_manager = CorpusManager::new(phink_file).unwrap();

let selector = create_test_selector();
corpus_manager.write_corpus_file(0, &selector)?;

let file_path = corpus_manager.corpus_dir.join("selector_0.bin");
assert!(file_path.exists());

let data = fs::read(file_path)?;
assert_eq!(data[5..9], selector.0);

Ok(())
}
}
16 changes: 9 additions & 7 deletions src/fuzzer/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::{
pretty_print,
timestamp,
},
envbuilder::EnvironmentBuilder,
environment::EnvironmentBuilder,
fuzz::FuzzingMode::{
ExecuteOneInput,
Fuzz,
Expand Down Expand Up @@ -92,6 +92,7 @@ impl Fuzzer {

fn should_stop_now(manager: &CampaignManager, messages: Vec<Message>) -> bool {
// todo: need to refactor this

messages.is_empty()
|| messages.iter().any(|payload| {
payload
Expand All @@ -113,9 +114,13 @@ impl Fuzzer {
let messages = PayloadCrafter::extract_all(self.ziggy_config.contract_path.to_owned())
.context("Couldn't extract all the messages selectors")?;

let payable_messages = PayloadCrafter::extract_payables(&contract_bridge.json_specs)
.context("Couldn't fetch payable messages")?;

let mut database = SelectorDatabase::new();
database.add_invariants(invariants);
database.add_messages(messages);
database.add_payables(payable_messages);

let manager = CampaignManager::new(
database.clone(),
Expand Down Expand Up @@ -215,17 +220,14 @@ mod tests {
selectors::database::SelectorDatabase,
},
fuzzer::{
envbuilder::EnvironmentBuilder,
environment::EnvironmentBuilder,
fuzz::Fuzzer,
manager::CampaignManager,
},
instrumenter::path::InstrumentedPath,
};
use contract_transcode::{
AccountId32,
ContractMessageTranscoder,
};
use frame_support::weights::Weight;
use contract_transcode::ContractMessageTranscoder;

use std::{
fs,
path::{
Expand Down
13 changes: 0 additions & 13 deletions src/fuzzer/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,6 @@ impl CampaignManager {
Arc::clone(&self.transcoder)
}

pub fn is_payable(&self, selector: &Selector) -> bool {
self.transcoder
.lock()
.unwrap()
.metadata()
.spec()
.messages()
.iter()
.find(|msg| msg.selector().to_bytes().eq(selector.as_ref()))
.map(|msg| msg.payable())
.unwrap_or(false)
}

pub fn check_invariants(
&self,
all_msg_responses: &[FullContractResponse],
Expand Down
2 changes: 1 addition & 1 deletion src/fuzzer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pub mod engine;
pub mod envbuilder;
pub mod environment;
pub mod fuzz;
pub mod manager;
pub mod parser;
17 changes: 4 additions & 13 deletions src/fuzzer/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,7 @@ pub fn parse_input(data: &[u8], manager: CampaignManager) -> OneInput {
let selector: [u8; 4] = encoded_message[0..4]
.try_into()
.expect("Slice conversion failed");
let sec = Selector::from(selector);

if !manager
.database()
.messages_with_invariants()
.unwrap()
.contains(&sec)
{
continue;
}

let slctr = Selector::from(selector);
let value: u32 = u32::from_ne_bytes(inkpayload[0..4].try_into().unwrap()); // todo: it's actually 16 not 4
let origin = match input.fuzz_option {
EnableOriginFuzzing => Origin(inkpayload[4]),
Expand All @@ -138,10 +128,11 @@ pub fn parse_input(data: &[u8], manager: CampaignManager) -> OneInput {
if fuzzdata.max_messages_per_exec != 0
&& input.messages.len() <= fuzzdata.max_messages_per_exec
{
println!("{:?}", metadata);
// println!("{:?}", metadata);

input.messages.push(Message {
is_payable: manager.is_payable(&sec),
// is_payable: manager.is_payable(&slctr),
is_payable: manager.database().is_payable(&slctr),
payload: encoded_message.into(),
value_token: value as u128,
message_metadata: metadata.clone(),
Expand Down

0 comments on commit 2557f9d

Please sign in to comment.