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

Code limit tool #42

Merged
merged 2 commits into from
Jan 13, 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 contracts/pairing/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/target
.snfoundry_cache
.snfoundry_cache
3 changes: 1 addition & 2 deletions contracts/pairing/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,4 @@ mod pairing {
res
}
}
}

}
78 changes: 78 additions & 0 deletions contracts/scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Code Limit

A Python script for analyzing the size and bytecode length of Sierra contract class files. This tool helps developers understand the size characteristics of their smart contracts by providing both the total bytecode size in bytes and the number of field elements (felts) in the program.

#### ⚠️ Important Note on Size Calculation

Please be aware: The bytecode size calculation provided by this tool should be used as a rough estimate only. Even when the tool reports a bytecode size around 49,000 felts, you may still encounter "code size limit" errors when attempting to declare the contract class.

## Prerequisites

- Python 3.x
- `jq` command-line tool installed on your system
- JSON-formatted contract class files

## Installation

1. Clone this repository or download `code_limit.py`
2. Ensure you have `jq` installed:
- For Ubuntu/Debian: `sudo apt-get install jq`
- For macOS: `brew install jq`
- For Windows: Download from the [official jq website](https://stedolan.github.io/jq/download/)

## Usage

Run it directly from the command line:

```bash
python code_limit.py
```

Before running, make sure to modify the `path_filename` variable in the `main()` function to point to your contract class JSON file:

```python
path_filename = "path/to/your/contract_class.json"
```

## Functions

### get_sierra_size(path_filename)

Calculates the total size in bytes of the Sierra program bytecode.

Parameters:

- `path_filename` (str): Path to the contract class JSON file

Returns:

- `int`: Total size in bytes, or `None` if an error occurs

### get_bytecode_length_with_jq(path_filename)

Uses `jq` to calculate the number of field elements in the Sierra program.

Parameters:

- `path_filename` (str): Path to the contract class JSON file

Returns:

- `int`: Number of field elements, or `None` if an error occurs

## Error Handling

The script handles various error cases:

- File not found
- Invalid JSON format
- Missing 'sierra_program' field
- JQ command failures
- General exceptions

## Example Output

```
Contract class size: 1,234 bytes
Contract Bytecode size (Number of felts in the program): 567
```
77 changes: 77 additions & 0 deletions contracts/scripts/code_limit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import json
import subprocess

def calculate_bytecode_size(hex_strings):
total_bytes = 0

for hex_str in hex_strings:
hex_str = hex_str.replace('0x', '')
num_bytes = len(hex_str) // 2
total_bytes += num_bytes

return total_bytes

def get_sierra_size(path_filename):
try:
with open(path_filename, 'r') as file:
data = json.load(file)

# Extract sierra_program array
if 'sierra_program' not in data:
raise KeyError("No 'sierra_program' found in JSON file")

sierra_program = data['sierra_program']

# Calculate total size
size = calculate_bytecode_size(sierra_program)
return size

except FileNotFoundError:
print(f"Error: File '{path_filename}' not found")
except json.JSONDecodeError:
print(f"Error: File '{path_filename}' is not valid JSON")
except KeyError as e:
print(f"Error: {str(e)}")
except Exception as e:
print(f"Unexpected error: {str(e)}")

return None

def get_bytecode_length_with_jq(path_filename):
"""Call the jq command to get the bytecode length."""
try:
# Run the jq command to get sierra_program length
result = subprocess.run(
['jq', '.sierra_program | length', path_filename],
capture_output=True,
text=True,
check=True
)

bytecode_length = int(result.stdout.strip())
return bytecode_length

except subprocess.CalledProcessError as e:
print(f"Error running jq: {e}")
except ValueError:
print("Error: Unable to parse jq output as an integer")
except Exception as e:
print(f"Unexpected error: {str(e)}")

return None

def main():
# Path to JSON file
path_filename = "path/to/contract_class.json"

size = get_sierra_size(path_filename)
if size is not None:
print(f"Contract class size: {size:,} bytes")

# Calculate size using jq
bytecode_length = get_bytecode_length_with_jq(path_filename)
if bytecode_length is not None:
print(f"Contract Bytecode size (Number of felts in the program): {bytecode_length}")

if __name__ == "__main__":
main()
145 changes: 67 additions & 78 deletions plonk-verifier/src/fields/fq_2.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -101,126 +101,115 @@ impl Fq2Utils of FieldUtils<Fq2, u384, CircuitModulus> {
impl Fq2Ops of FieldOps<Fq2, CircuitModulus> {
// #[inline(always)]
fn add(self: Fq2, rhs: Fq2, m: CircuitModulus) -> Fq2 {
let (c0, c1) = add_circuit();

let outputs = match (c0, c1)
.new_inputs()
.next(self.c0.c0)
.next(self.c1.c0)
.next(rhs.c0.c0)
.next(rhs.c1.c0)
.done()
.eval(m) {
Result::Ok(outputs) => { outputs },
Result::Err(_) => { panic!("Expected success") }
let (c0, c1) = add_circuit();

let outputs =
match (c0, c1)
.new_inputs()
.next(self.c0.c0)
.next(self.c1.c0)
.next(rhs.c0.c0)
.next(rhs.c1.c0)
.done()
.eval(m) {
Result::Ok(outputs) => { outputs },
Result::Err(_) => { panic!("Expected success") }
};

fq2(outputs.get_output(c0), outputs.get_output(c1))
}

// #[inline(always)]
fn sub(self: Fq2, rhs: Fq2, m: CircuitModulus) -> Fq2 {
let (c0, c1) = sub_circuit();

let outputs = match (c0, c1)
.new_inputs()
.next(self.c0.c0)
.next(self.c1.c0)
.next(rhs.c0.c0)
.next(rhs.c1.c0)
.done()
.eval(m) {
Result::Ok(outputs) => { outputs },
Result::Err(_) => { panic!("Expected success") }
let (c0, c1) = sub_circuit();

let outputs =
match (c0, c1)
.new_inputs()
.next(self.c0.c0)
.next(self.c1.c0)
.next(rhs.c0.c0)
.next(rhs.c1.c0)
.done()
.eval(m) {
Result::Ok(outputs) => { outputs },
Result::Err(_) => { panic!("Expected success") }
};

fq2(outputs.get_output(c0), outputs.get_output(c1))
}

// #[inline(always)]
fn mul(self: Fq2, rhs: Fq2, m: CircuitModulus) -> Fq2 {
let (c0, c1) = mul_circuit();

let outputs = match (c0, c1)
.new_inputs()
.next(self.c0.c0)
.next(self.c1.c0)
.next(rhs.c0.c0)
.next(rhs.c1.c0)
.done()
.eval(m) {
Result::Ok(outputs) => { outputs },
Result::Err(_) => { panic!("Expected success") }
let (c0, c1) = mul_circuit();

let outputs =
match (c0, c1)
.new_inputs()
.next(self.c0.c0)
.next(self.c1.c0)
.next(rhs.c0.c0)
.next(rhs.c1.c0)
.done()
.eval(m) {
Result::Ok(outputs) => { outputs },
Result::Err(_) => { panic!("Expected success") }
};

fq2(outputs.get_output(c0), outputs.get_output(c1))
}

// #[inline(always)]
fn div(self: Fq2, rhs: Fq2, m: CircuitModulus) -> Fq2 {
let (c0, c1) = div_circuit();

let outputs = match (c0, c1)
.new_inputs()
.next(self.c0.c0)
.next(self.c1.c0)
.next(rhs.c0.c0)
.next(rhs.c1.c0)
.done()
.eval(m) {
Result::Ok(outputs) => { outputs },
Result::Err(_) => { panic!("Expected success") }
let (c0, c1) = div_circuit();

let outputs =
match (c0, c1)
.new_inputs()
.next(self.c0.c0)
.next(self.c1.c0)
.next(rhs.c0.c0)
.next(rhs.c1.c0)
.done()
.eval(m) {
Result::Ok(outputs) => { outputs },
Result::Err(_) => { panic!("Expected success") }
};

fq2(outputs.get_output(c0), outputs.get_output(c1))
}

// #[inline(always)]
fn neg(self: Fq2, m: CircuitModulus) -> Fq2 {
let (c0, c1) = neg_circuit();

let outputs = match (c0, c1)
.new_inputs()
.next(self.c0.c0)
.next(self.c1.c0)
.done()
.eval(m) {
Result::Ok(outputs) => { outputs },
Result::Err(_) => { panic!("Expected success") }
let (c0, c1) = neg_circuit();

let outputs = match (c0, c1).new_inputs().next(self.c0.c0).next(self.c1.c0).done().eval(m) {
Result::Ok(outputs) => { outputs },
Result::Err(_) => { panic!("Expected success") }
};

fq2(outputs.get_output(c0), outputs.get_output(c1))
}

// #[inline(always)]
fn sqr(self: Fq2, m: CircuitModulus) -> Fq2 {
let (c0, c1) = sqr_circuit();

let outputs = match (c0, c1)
.new_inputs()
.next(self.c0.c0)
.next(self.c1.c0)
.done()
.eval(m) {
Result::Ok(outputs) => { outputs },
Result::Err(_) => { panic!("Expected success") }
let (c0, c1) = sqr_circuit();

let outputs = match (c0, c1).new_inputs().next(self.c0.c0).next(self.c1.c0).done().eval(m) {
Result::Ok(outputs) => { outputs },
Result::Err(_) => { panic!("Expected success") }
};

fq2(outputs.get_output(c0), outputs.get_output(c1))
}

// #[inline(always)]
fn inv(self: Fq2, m: CircuitModulus) -> Fq2 {
let (c0, c1) = inv_circuit();

let outputs = match (c0, c1)
.new_inputs()
.next(self.c0.c0)
.next(self.c1.c0)
.done()
.eval(m) {
Result::Ok(outputs) => { outputs },
Result::Err(_) => { panic!("Expected success") }
let (c0, c1) = inv_circuit();

let outputs = match (c0, c1).new_inputs().next(self.c0.c0).next(self.c1.c0).done().eval(m) {
Result::Ok(outputs) => { outputs },
Result::Err(_) => { panic!("Expected success") }
};

fq2(outputs.get_output(c0), outputs.get_output(c1))
Expand Down
Loading