vyper_rs/
utils.rs

1//! Utilities offered by the crate.
2
3use std::{
4    fs::read_dir,
5    io::Error,
6    path::PathBuf,
7};
8
9use crate::vyper_errors::VyperErrors;
10
11/// Parses the ERC-5202 bytecode container format for indexing blueprint contracts.
12///
13/// "A blueprint contract MUST use the preamble 0xFE71<version bits><length encoding bits>. 6 bits are allocated to the version, and 2 bits to the length encoding. The first version begins at 0 (0b000000), and versions increment by 1. The value 0b11 for <length encoding bits> is reserved. In the case that the length bits are 0b11, the third byte is considered a continuation byte (that is, the version requires multiple bytes to encode). The exact encoding of a multi-byte version is left to a future ERC.
14/// A blueprint contract MUST contain at least one byte of initcode.
15/// A blueprint contract MAY insert any bytes (data or code) between the version byte(s) and the initcode. If such variable length data is used, the preamble must be 0xFE71<version bits><length encoding bits><length bytes><data>. The <length encoding bits> represent a number between 0 and 2 (inclusive) describing how many bytes <length bytes> takes, and <length bytes> is the big-endian encoding of the number of bytes that <data> takes."
16///
17/// "ERC-5202: Blueprint contract format," Ethereum Improvement Proposals, no. 5202, June 2022. [Online serial].
18/// Available: https://eips.ethereum.org/EIPS/eip-5202.
19
20#[derive(Clone, Debug, Eq, PartialEq)]
21pub struct Blueprint {
22    pub erc_version: u8,
23    pub preamble_data: Option<Vec<u8>>,
24    pub initcode: Vec<u8>,
25}
26
27pub fn parse_blueprint(bytecode: &[u8]) -> Result<Blueprint, VyperErrors> {
28    if bytecode.is_empty() {
29        Err(VyperErrors::BlueprintError("Empty Bytecode".to_owned()))?
30    }
31    if &bytecode[0..2] != b"\xFE\x71" {
32        Err(VyperErrors::BlueprintError("Not a blueprint!".to_owned()))?
33    }
34
35    let erc_version = (&bytecode[2] & 0b11111100) >> 2;
36    let n_length_bytes = &bytecode[2] & 0b11;
37
38    if n_length_bytes == 0b11 {
39        Err(VyperErrors::BlueprintError("Reserved bits are set".to_owned()))?
40    }
41
42    let size_temp = bytecode[3..(3 + n_length_bytes as usize)].to_vec();
43    let data_length = match size_temp.len() {
44        0 => 0,
45        _ => {
46            let size: String = hex::encode(&size_temp);
47            match u32::from_str_radix(&size, size_temp.len() as u32 * 8u32) {
48                Ok(num) => num,
49                Err(e) => Err(VyperErrors::IntParseError(e))?,
50            }
51        }
52    };
53
54    let preamble_data: Option<Vec<u8>> = match data_length {
55        0 => None,
56        _ => {
57            let data_start = 3 + n_length_bytes as usize;
58            Some(bytecode[data_start..data_start + data_length as usize].to_vec())
59        }
60    };
61
62    let initcode =
63        bytecode[3 + n_length_bytes as usize + data_length as usize..].to_vec();
64    match initcode.is_empty() {
65        true => {
66            Err(VyperErrors::BlueprintError("Empty Initcode!".to_owned()))?
67        }
68        false => Ok(Blueprint{erc_version, preamble_data, initcode}),
69    }
70}
71
72/// Scans current directory, looks for /contracts or /src folder and searches them too if they
73/// exist. Returns a Vec of PathBufs to any Vyper contract found. 
74pub async fn scan_workspace(root: PathBuf) -> Result<Vec<PathBuf>, Error> {
75    let cwd = root.clone();
76    let h1 = tokio::spawn(async move { get_contracts_in_dir(cwd) });
77    let hh_ape = root.join("contracts");
78    let h2 = tokio::spawn(async move { get_contracts_in_dir(hh_ape) });
79    let foundry = root.join("src");
80    let h3 = tokio::spawn(async move { get_contracts_in_dir(foundry) });
81    let mut res = Vec::new();
82    for i in [h1, h2, h3].into_iter() {
83        let result = match i.await {
84            Ok(Ok(x)) => x,
85            _ => Vec::new(),
86        };
87        res.push(result)
88    }
89    Ok(res.into_iter().flatten().collect::<Vec<PathBuf>>())
90}
91
92/// Scans current directory, looks for any vyper contracts and returns a Vec of PathBufs to any
93/// contracts found. 
94pub fn get_contracts_in_dir(dir: PathBuf) -> Result<Vec<PathBuf>, Error> {
95    let files = read_dir(dir)?;
96    let contracts = files.into_iter().try_fold(
97        Vec::new(),
98        |mut acc, x| -> Result<Vec<PathBuf>, Error> {
99            let file = x?;
100            if file.path().ends_with(".vy") {
101                acc.push(file.path())
102            }
103            Ok(acc)
104        },
105    )?;
106    Ok(contracts)
107}