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

[Fix] Limit program size to 100KBs #2384

Merged
merged 5 commits into from Mar 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions console/network/src/lib.rs
Expand Up @@ -144,10 +144,21 @@ pub trait Network:
/// The maximum number of entries in a record.
const MAX_RECORD_ENTRIES: usize = Self::MIN_RECORD_ENTRIES.saturating_add(Self::MAX_DATA_ENTRIES);

/// The maximum program size in bytes.
const MAX_PROGRAM_BYTE_SIZE: usize = 3_500_000; // 3.5 MB
/// The maximum program size in characters.
const MAX_PROGRAM_CHAR_SIZE: usize = 7_000_000; // 7 MB
howardwu marked this conversation as resolved.
Show resolved Hide resolved

/// The maximum number of mappings in a program.
const MAX_MAPPINGS: usize = 31;
/// The maximum number of functions in a program.
const MAX_FUNCTIONS: usize = 31;
/// The maximum number of structs in a program.
const MAX_STRUCTS: usize = 10 * Self::MAX_FUNCTIONS;
/// The maximum number of records in a program.
const MAX_RECORDS: usize = 10 * Self::MAX_FUNCTIONS;
/// The maximum number of closures in a program.
const MAX_CLOSURES: usize = 2 * Self::MAX_FUNCTIONS;
/// The maximum number of operands in an instruction.
const MAX_OPERANDS: usize = Self::MAX_INPUTS;
/// The maximum number of instructions in a closure or function.
Expand Down
9 changes: 9 additions & 0 deletions synthesizer/program/src/lib.rs
Expand Up @@ -362,6 +362,9 @@ impl<N: Network, Instruction: InstructionTrait<N>, Command: CommandTrait<N>> Pro
// Retrieve the struct name.
let struct_name = *struct_.name();

// Ensure the program has not exceeded the maximum number of structs.
ensure!(self.structs.len() < N::MAX_STRUCTS, "Program exceeds the maximum number of structs.");

// Ensure the struct name is new.
ensure!(self.is_unique_name(&struct_name), "'{struct_name}' is already in use.");
// Ensure the struct name is not a reserved opcode.
Expand Down Expand Up @@ -420,6 +423,9 @@ impl<N: Network, Instruction: InstructionTrait<N>, Command: CommandTrait<N>> Pro
// Retrieve the record name.
let record_name = *record.name();

// Ensure the program has not exceeded the maximum number of records.
ensure!(self.records.len() < N::MAX_RECORDS, "Program exceeds the maximum number of records.");

// Ensure the record name is new.
ensure!(self.is_unique_name(&record_name), "'{record_name}' is already in use.");
// Ensure the record name is not a reserved opcode.
Expand Down Expand Up @@ -480,6 +486,9 @@ impl<N: Network, Instruction: InstructionTrait<N>, Command: CommandTrait<N>> Pro
// Retrieve the closure name.
let closure_name = *closure.name();

// Ensure the program has not exceeded the maximum number of closures.
ensure!(self.closures.len() < N::MAX_CLOSURES, "Program exceeds the maximum number of closures.");

// Ensure the closure name is new.
ensure!(self.is_unique_name(&closure_name), "'{closure_name}' is already in use.");
// Ensure the closure name is not a reserved opcode.
Expand Down
112 changes: 112 additions & 0 deletions synthesizer/program/src/parse.rs
Expand Up @@ -106,10 +106,16 @@ impl<N: Network, Instruction: InstructionTrait<N>, Command: CommandTrait<N>> Fro

/// Returns a program from a string literal.
fn from_str(string: &str) -> Result<Self> {
// Ensure the raw program string is less than MAX_PROGRAM_CHAR_SIZE.
ensure!(string.len() <= N::MAX_PROGRAM_CHAR_SIZE, "Program exceeds N::MAX_PROGRAM_CHAR_SIZE.");
howardwu marked this conversation as resolved.
Show resolved Hide resolved

match Self::parse(string) {
Ok((remainder, object)) => {
// Ensure the remainder is empty.
ensure!(remainder.is_empty(), "Failed to parse string. Remaining invalid string is: \"{remainder}\"");
// Ensure the parsed program is less than MAX_PROGRAM_BYTE_SIZE.
let program_byte_len = object.to_bytes_le()?.len();
ensure!(program_byte_len <= N::MAX_PROGRAM_BYTE_SIZE, "Program exceeds N::MAX_PROGRAM_BYTE_SIZE.");
// Return the object.
Ok(object)
}
Expand Down Expand Up @@ -255,4 +261,110 @@ function compute:

Ok(())
}

#[test]
fn test_program_size() {
let long_name = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa";

// Helper function to generate large structs.
let gen_struct_string = |n: usize| -> String {
let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_CHAR_SIZE);
for i in 0..n {
s.push_str(&format!("struct m{}:\n", i));
for j in 0..CurrentNetwork::MAX_DATA_ENTRIES {
s.push_str(&format!(" {}{} as u128;\n", long_name, j));
}
}
s
};

// Helper function to generate large records.
let gen_record_string = |n: usize| -> String {
let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_CHAR_SIZE);
for i in 0..n {
s.push_str(&format!("record r{}:\n owner as address.private;\n", i));
for j in 0..CurrentNetwork::MAX_DATA_ENTRIES {
s.push_str(&format!(" {}{} as u128.private;\n", long_name, j));
}
}
s
};

// Helper function to generate large mappings.
let gen_mapping_string = |n: usize| -> String {
let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_CHAR_SIZE);
for i in 0..n {
s.push_str(&format!(
"mapping {}{}:\n key as field.public;\n value as field.public;\n",
long_name, i
));
}
s
};

// Helper function to generate large closures.
let gen_closure_string = |n: usize| -> String {
let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_CHAR_SIZE);
for i in 0..n {
s.push_str(&format!("closure c{}:\n input r0 as u128;\n", i));
for j in 0..4000 {
s.push_str(&format!(" add r0 r0 into r{};\n", j));
}
s.push_str(&format!(" output r{} as u128;\n", 4000));
}
s
};

// Helper function to generate large functions.
let gen_function_string = |n: usize| -> String {
let mut s = String::with_capacity(CurrentNetwork::MAX_PROGRAM_CHAR_SIZE);
for i in 0..n {
s.push_str(&format!("function f{}:\n add 1u128 1u128 into r0;\n", i));
for j in 0..4000 {
s.push_str(&format!(" add r0 r0 into r{j};\n"));
}
}
s
};

// Helper function to generate and parse a program.
let test_parse = |program: String, should_succeed: bool| {
let program = format!("program to_parse.aleo; {}", program);
let result = Program::<CurrentNetwork>::from_str(&program);
assert_eq!(result.is_ok(), should_succeed);
};

// A program with MAX_STRUCTS should succeed.
test_parse(gen_struct_string(CurrentNetwork::MAX_STRUCTS), true);
// A program with more than MAX_STRUCTS should fail.
test_parse(gen_struct_string(CurrentNetwork::MAX_STRUCTS + 1), false);
// A program with MAX_RECORDS should succeed.
test_parse(gen_record_string(CurrentNetwork::MAX_RECORDS), true);
// A program with more than MAX_RECORDS should fail.
test_parse(gen_record_string(CurrentNetwork::MAX_RECORDS + 1), false);
// A program with MAX_MAPPINGS should succeed.
test_parse(gen_mapping_string(CurrentNetwork::MAX_MAPPINGS), true);
// A program with more than MAX_MAPPINGS should fail.
test_parse(gen_mapping_string(CurrentNetwork::MAX_MAPPINGS + 1), false);
// A program with MAX_CLOSURES should succeed.
test_parse(gen_closure_string(CurrentNetwork::MAX_CLOSURES), true);
// A program with more than MAX_CLOSURES should fail.
test_parse(gen_closure_string(CurrentNetwork::MAX_CLOSURES + 1), false);
// A program with MAX_FUNCTIONS should succeed.
test_parse(gen_function_string(CurrentNetwork::MAX_FUNCTIONS), true);
// A program with more than MAX_FUNCTIONS should fail.
test_parse(gen_function_string(CurrentNetwork::MAX_FUNCTIONS + 1), false);

// Initialize a program which is too big.
let program_too_big = format!(
"{} {} {} {} {}",
gen_struct_string(CurrentNetwork::MAX_STRUCTS),
gen_record_string(CurrentNetwork::MAX_RECORDS),
gen_mapping_string(CurrentNetwork::MAX_MAPPINGS),
gen_closure_string(CurrentNetwork::MAX_CLOSURES),
gen_function_string(CurrentNetwork::MAX_FUNCTIONS)
);
// A program which is too big should fail.
test_parse(program_too_big, false);
}
}