use crate::{
cli::config::OriginFuzzingOption,
contract::{
remote::{
BalanceOf,
ContractResponse,
FullContractResponse,
},
runtime::Runtime,
selectors::selector::Selector,
},
fuzzer::manager::CampaignManager,
ResultOf,
};
use contract_transcode::{
ContractMessageTranscoder,
Value,
};
use prettytable::{
Cell,
Row,
Table,
};
use serde_derive::Serialize;
use sp_core::crypto::AccountId32;
use std::fmt::{
Display,
Formatter,
};
use OriginFuzzingOption::{
DisableOriginFuzzing,
EnableOriginFuzzing,
};
pub const DELIMITER: [u8; 8] = [42; 8]; pub const MIN_SEED_LEN: usize = 9;
#[derive(Clone, Copy)]
pub struct Data<'a> {
pub data: &'a [u8],
pub pointer: usize,
pub size: usize,
pub max_messages_per_exec: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct Message {
pub is_payable: bool,
pub payload: Vec<u8>,
pub value_token: BalanceOf<Runtime>,
pub message_metadata: Value,
pub origin: Origin,
}
impl Message {
pub fn display_with_reply(&self, reply: &ContractResponse) -> String {
format!(
"⛽️ Gas required: {}\n\
🔥 Gas consumed: {}\n\
🧑 Origin: {:?} ({})\n\
💾 Storage deposit: {:?}{}",
reply.gas_required,
reply.gas_consumed,
self.origin,
AccountId32::new([self.origin.into(); 32]),
reply.storage_deposit,
if self.is_payable {
format!(
"\n💸 Message was payable and {} units were transferred",
self.value_token
)
} else {
String::new()
}
)
}
pub fn print(&self) -> String {
format!(
"Payload:\t0x{}\n\
Origin:\t{:?} (identifier: {})\n\
{}\
Message:\t{}\n\n",
hex::encode(&self.payload),
AccountId32::new([self.origin.into(); 32]),
self.origin.0,
if self.is_payable {
format!("Transfered: {}\n", self.value_token)
} else {
String::new()
},
self.message_metadata
)
}
}
impl Display for Message {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.message_metadata.to_string().as_str())
}
}
#[derive(Debug, Clone, Serialize)]
pub struct OneInput {
pub messages: Vec<Message>,
pub fuzz_option: OriginFuzzingOption,
pub raw_binary: Vec<u8>,
}
impl OneInput {
#[allow(dead_code)]
pub fn pretty_print(&self, responses: Vec<FullContractResponse>) {
println!("\n🌱 Executing new seed");
let mut table = Table::new();
table.add_row(Row::new(vec![Cell::new("Message"), Cell::new("Details")]));
for (response, message) in responses.iter().zip(&self.messages) {
let call_description = message.message_metadata.to_string();
let debug = message.display_with_reply(response.get());
table.add_row(Row::new(vec![
Cell::new(&call_description),
Cell::new(&debug),
]));
}
table.printstd();
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct Origin(pub u8);
impl Default for Origin {
fn default() -> Self {
Origin(1)
}
}
impl From<u8> for Origin {
fn from(value: u8) -> Self {
Origin(value)
}
}
impl From<Origin> for u8 {
fn from(origin: Origin) -> Self {
origin.0
}
}
impl Data<'_> {
fn size_limit_reached(&self) -> bool {
self.size >= self.max_messages_per_exec
}
}
impl<'a> Iterator for Data<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.size_limit_reached() {
return None;
}
if self.max_messages_per_exec == 1 {
let res = &self.data[self.pointer..];
self.pointer = self.data.len();
self.size += 1;
return if res.len() >= MIN_SEED_LEN {
Some(res)
} else {
None
};
}
loop {
if self.data.len() <= self.pointer {
return None;
}
let next_delimiter = self.data[self.pointer..]
.windows(DELIMITER.len())
.position(|window| window == DELIMITER);
let next_pointer = match next_delimiter {
Some(delimiter) => self.pointer + delimiter,
None => self.data.len(),
};
let res = &self.data[self.pointer..next_pointer];
self.pointer = next_pointer + DELIMITER.len();
if res.len() >= MIN_SEED_LEN {
self.size += 1;
return Some(res);
}
}
}
}
pub fn try_parse_input(bytes: &[u8], manager: CampaignManager) -> Option<OneInput> {
let config = manager.config();
let data = Data {
data: bytes,
pointer: 0,
size: 0,
max_messages_per_exec: config.max_messages_per_exec.unwrap_or_default(),
};
let mut input = OneInput {
messages: vec![],
fuzz_option: config.should_fuzz_origin(),
raw_binary: Vec::new(),
};
let arc = manager.transcoder();
let guard = arc.try_lock().expect("Failed on `try_lock`");
for payload in data {
let origin = match input.fuzz_option {
EnableOriginFuzzing => Origin(payload[4]),
DisableOriginFuzzing => Origin::default(),
};
let mut encoded_message = vec![0u8; payload.len() - 5];
encoded_message.copy_from_slice(&payload[5..]);
let selector: [u8; 4] = encoded_message[0..4].try_into().expect("[0..4] to u8 fail");
let slctr = Selector::from(selector);
let db = manager.database();
if db.contains_invariant(&slctr) || !db.contains_message(&slctr) {
return None;
}
let mut encoded_cloned = encoded_message.clone();
match decode_contract_message(&guard, &mut encoded_cloned) {
Ok(message_metadata) => {
if data.max_messages_per_exec != 0
&& input.messages.len() <= data.max_messages_per_exec
{
let is_payable: bool = db.is_payable(&slctr);
let mut value_token: u128 = 0;
if is_payable {
value_token = u32::from_ne_bytes(payload[0..4].try_into().unwrap()) as u128 }
input.raw_binary = Vec::from(bytes);
input.messages.push(Message {
is_payable,
payload: encoded_message,
value_token,
message_metadata,
origin,
});
}
}
Err(_) => {
return None;
}
}
}
if !input.messages.is_empty() {
return Some(input);
}
None
}
pub fn decode_contract_message(
guard: &ContractMessageTranscoder,
data: &mut Vec<u8>,
) -> ResultOf<Value> {
use contract_transcode::Map;
use std::io::Read;
let mut data_as_slice = data.as_slice();
let mut msg_selector: [u8; 4] = [0u8; 4];
data_as_slice.read_exact(&mut msg_selector)?;
let msg_spec = guard
.metadata()
.spec()
.messages()
.iter()
.find(|x| msg_selector == x.selector().to_bytes())
.ok_or_else(|| {
anyhow::anyhow!(
"Message with selector {} not found in contract metadata",
hex::encode_upper(msg_selector)
)
})?;
let mut args = Vec::new();
for arg in msg_spec.args() {
let name = arg.label().to_string();
let value = guard.decode(arg.ty().ty().id, &mut data_as_slice)?;
args.push((Value::String(name), value));
}
if !data_as_slice.is_empty() {
return Err(anyhow::anyhow!(
"input length was longer than expected by {} byte(s).\n `{}` bytes were left unread",
data_as_slice.len(),
hex::encode_upper(data)
));
}
let name = msg_spec.label().to_string();
let map = Map::new(Some(&name), args.into_iter().collect());
Ok(Value::Map(map))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_data_iterator() {
let input = [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 42, 42, 42, 42, 42, 42, 42, 42, 5, 6, 7, 23, 123, 1, 8,
12, 13, 14,
];
let data = Data {
data: &input,
pointer: 0,
size: 0,
max_messages_per_exec: 2,
};
let result: Vec<&[u8]> = data.collect();
assert_eq!(
result,
vec![
&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
&[5, 6, 7, 23, 123, 1, 8, 12, 13, 14]
]
);
}
#[test]
fn test_data_size_limit() {
let input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let mut data = Data {
data: &input,
pointer: 0,
size: 0,
max_messages_per_exec: 1,
};
assert_eq!(data.next(), Some(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10][..]));
assert_eq!(data.next(), None);
}
#[test]
fn test_origin_default() {
assert_eq!(Origin::default(), Origin(1));
}
#[test]
fn test_origin_from_u8() {
assert_eq!(Origin::from(5), Origin(5));
}
#[test]
fn test_u8_from_origin() {
assert_eq!(u8::from(Origin(3)), 3);
}
}