|  | 
|  | 1 | +extern crate parity_wasm; | 
|  | 2 | +extern crate pwasm_utils; | 
|  | 3 | + | 
|  | 4 | +use parity_wasm::elements::{Module,MemoryType}; | 
|  | 5 | +use clap::{Arg, App, SubCommand, ArgMatches}; | 
|  | 6 | +use parity_wasm::elements::{Instructions, Instruction}; | 
|  | 7 | + | 
|  | 8 | +fn main() { | 
|  | 9 | +    let matches = App::new("cap9-build") | 
|  | 10 | +            .version("0.2.0") | 
|  | 11 | +            .author("Cap9 <[email protected]>") | 
|  | 12 | +            .about("A command-line interface for linking Cap9 procedures.") | 
|  | 13 | +            .subcommand(SubCommand::with_name("build-proc") | 
|  | 14 | +                .about("Convert a regular contract into a cap9 procedure.") | 
|  | 15 | +                .arg(Arg::with_name("INPUT-FILE") | 
|  | 16 | +                    .required(true) | 
|  | 17 | +                    .help("input file")) | 
|  | 18 | +                .arg(Arg::with_name("OUTPUT-FILE") | 
|  | 19 | +                    .required(true) | 
|  | 20 | +                    .help("output file"))) | 
|  | 21 | +            .subcommand(SubCommand::with_name("set-mem") | 
|  | 22 | +                .about("Set the number of memory pages in a procedure.") | 
|  | 23 | +                .arg(Arg::with_name("INPUT-FILE") | 
|  | 24 | +                    .required(true) | 
|  | 25 | +                    .help("input file")) | 
|  | 26 | +                .arg(Arg::with_name("OUTPUT-FILE") | 
|  | 27 | +                    .required(true) | 
|  | 28 | +                    .help("output file")) | 
|  | 29 | +                .arg(Arg::with_name("pages") | 
|  | 30 | +                    .short("p") | 
|  | 31 | +                    .long("pages") | 
|  | 32 | +                    .value_name("PAGES") | 
|  | 33 | +                    .required(true) | 
|  | 34 | +                    .help("Number of pages to set the memory to"))) | 
|  | 35 | +            .get_matches(); | 
|  | 36 | + | 
|  | 37 | +    match matches.subcommand() { | 
|  | 38 | +        ("build-proc",  Some(opts)) => { | 
|  | 39 | +            let input_path = opts.value_of("INPUT-FILE").expect("input file is required"); | 
|  | 40 | +            let output_path = opts.value_of("OUTPUT-FILE").expect("output path is required"); | 
|  | 41 | + | 
|  | 42 | +            let module = parity_wasm::deserialize_file(input_path).expect("parsing of input failed"); | 
|  | 43 | +            let new_module = contract_build(module); | 
|  | 44 | +            parity_wasm::serialize_to_file(output_path, new_module).expect("serialising to output failed"); | 
|  | 45 | +        }, | 
|  | 46 | +        ("set-mem",  Some(opts)) => { | 
|  | 47 | +            let input_path = opts.value_of("INPUT-FILE").expect("input file is required"); | 
|  | 48 | +            let output_path = opts.value_of("OUTPUT-FILE").expect("output path is required"); | 
|  | 49 | +            let mem_pages = opts.value_of("pages").expect("number of memory pages is required"); | 
|  | 50 | + | 
|  | 51 | +            let module = parity_wasm::deserialize_file(input_path).expect("parsing of input failed"); | 
|  | 52 | +            let new_module = set_mem(module, mem_pages.parse().expect("expected number for number of pages")); | 
|  | 53 | +            parity_wasm::serialize_to_file(output_path, new_module).expect("serialising to output failed"); | 
|  | 54 | +        }, | 
|  | 55 | +        _ => panic!("unknown subcommand") | 
|  | 56 | +    } | 
|  | 57 | +} | 
|  | 58 | + | 
|  | 59 | + | 
|  | 60 | +/// Perform the operations necessary for cap9 procedures. | 
|  | 61 | +fn contract_build(module: Module) -> Module { | 
|  | 62 | + | 
|  | 63 | +    // TODO: we need to make sure these values never change between now and when | 
|  | 64 | +    // we use them. In the current set up they will not, but it is fragile, | 
|  | 65 | +    // there are changes that could be introduced which would change this. | 
|  | 66 | +    let syscall_instructions_res = get_syscall_instructions(&module); | 
|  | 67 | + | 
|  | 68 | +    // TODO: what is the index of this newly added function? | 
|  | 69 | +    let mut new_module_builder = parity_wasm::builder::from_module(module); | 
|  | 70 | +    // Add the syscall function, if applicable. | 
|  | 71 | +    let mut new_module = if let Ok(syscall_instructions) = syscall_instructions_res { | 
|  | 72 | +        new_module_builder | 
|  | 73 | +            .function() | 
|  | 74 | +                .signature() | 
|  | 75 | +                    .with_param(parity_wasm::elements::ValueType::I32) | 
|  | 76 | +                    .with_param(parity_wasm::elements::ValueType::I32) | 
|  | 77 | +                    .with_param(parity_wasm::elements::ValueType::I32) | 
|  | 78 | +                    .with_param(parity_wasm::elements::ValueType::I32) | 
|  | 79 | +                    .with_return_type(Some(parity_wasm::elements::ValueType::I32)) | 
|  | 80 | +                    .build() | 
|  | 81 | +                .body() | 
|  | 82 | +                    .with_instructions(syscall_instructions) | 
|  | 83 | +                    .build() | 
|  | 84 | +                .build() | 
|  | 85 | +            .build() | 
|  | 86 | +    } else { | 
|  | 87 | +        new_module_builder.build() | 
|  | 88 | +    }; | 
|  | 89 | + | 
|  | 90 | +    // TODO: robustly determine the function index of the function we just | 
|  | 91 | +    // added. I think at this point it's simply the last funciton added, thereby | 
|  | 92 | +    // functions_space - 1, but this is not guaranteed anywhere. | 
|  | 93 | +    let added_syscall_index = new_module.functions_space() - 1; | 
|  | 94 | + | 
|  | 95 | +    // If we find cap9_syscall_low as an import, we need to replace all | 
|  | 96 | +    // references to it with a reference to this newly added function, and | 
|  | 97 | +    // remove the import. Once we replace the internal references and run optimize, it will be removed anyway. | 
|  | 98 | +    let cap9_syscall_low_index = find_import(&new_module, "env", "cap9_syscall_low"); | 
|  | 99 | +    match cap9_syscall_low_index { | 
|  | 100 | +        None => (), | 
|  | 101 | +        Some(syscall_index) => { | 
|  | 102 | +            // Search though the code of each function, if we encounter a | 
|  | 103 | +            // Call(syscall_index), replace it with Call(added_syscall_index). | 
|  | 104 | +            // TODO: investigate the use of CallIndirect | 
|  | 105 | +            for f in new_module.code_section_mut().unwrap().bodies_mut().iter_mut() { | 
|  | 106 | +                for i in 0..f.code().elements().len() { | 
|  | 107 | +                    let instruction = &f.code().elements()[i]; | 
|  | 108 | +                    if instruction == &Instruction::Call(syscall_index) { | 
|  | 109 | +                        f.code_mut().elements_mut()[i] = Instruction::Call(added_syscall_index as u32); | 
|  | 110 | +                    } | 
|  | 111 | +                } | 
|  | 112 | +            } | 
|  | 113 | +        } | 
|  | 114 | +    } | 
|  | 115 | + | 
|  | 116 | +    // Next we want to delete dummy_syscall if it exists. First we find it among | 
|  | 117 | +    // the exports (if it doesn't exist we don't need to do anything). We take | 
|  | 118 | +    // the reference of the export (i.e. the function it exports) and delete | 
|  | 119 | +    // both that function and the export. One way to do this would be to delete | 
|  | 120 | +    // the export and run the parity's optimizer again. | 
|  | 121 | +    // 1. Get the index of the export | 
|  | 122 | +    if let Some(dummy_syscall_export_index) = find_export(&new_module, "dummy_syscall") { | 
|  | 123 | +        // println!("dummy_syscall_export_index: {}", dummy_syscall_export_index); | 
|  | 124 | +        // 2. Delete the export | 
|  | 125 | +        new_module.export_section_mut().unwrap().entries_mut().remove(dummy_syscall_export_index as usize); | 
|  | 126 | +    } | 
|  | 127 | +    // 3. At this stage the dummy_syscall function still exists internally. We | 
|  | 128 | +    //    can't use the same remove procedure without screwing up the internal | 
|  | 129 | +    //    references, so we will just run the parity optmizer again for now to | 
|  | 130 | +    //    let it deal with that. | 
|  | 131 | +    pwasm_utils::optimize(&mut new_module, vec!["call","deploy"]).unwrap(); | 
|  | 132 | +    new_module | 
|  | 133 | +} | 
|  | 134 | + | 
|  | 135 | +fn set_mem(mut module: Module, num_pages: u32) -> Module { | 
|  | 136 | +    // We want to find the single memory section, and change it from its current | 
|  | 137 | +    // value to the one we've requested. | 
|  | 138 | +    let mut mem_entry: &mut Vec<MemoryType> = module.memory_section_mut().unwrap().entries_mut(); | 
|  | 139 | +    mem_entry[0] = parity_wasm::elements::MemoryType::new(num_pages,None); | 
|  | 140 | +    module | 
|  | 141 | +} | 
|  | 142 | + | 
|  | 143 | +// Find the function index of an import | 
|  | 144 | +fn find_import(module: &Module, mod_name: &str, field_name: &str) -> Option<u32> { | 
|  | 145 | +    let imports = module.import_section().unwrap().entries(); | 
|  | 146 | +    for (i,import) in imports.iter().enumerate() { | 
|  | 147 | +        if import.module() == mod_name && import.field() == field_name { | 
|  | 148 | +            return Some(i as u32); | 
|  | 149 | +        } | 
|  | 150 | +    } | 
|  | 151 | +    return None; | 
|  | 152 | +} | 
|  | 153 | + | 
|  | 154 | +// Find the function index of an export | 
|  | 155 | +fn find_export(module: &Module, field_name: &str) -> Option<u32> { | 
|  | 156 | +    let exports = module.export_section().unwrap().entries(); | 
|  | 157 | +    for (i,export) in exports.iter().enumerate() { | 
|  | 158 | +        if export.field() == field_name { | 
|  | 159 | +            return Some(i as u32); | 
|  | 160 | +        } | 
|  | 161 | +    } | 
|  | 162 | +    return None; | 
|  | 163 | +} | 
|  | 164 | + | 
|  | 165 | +enum SysCallError { | 
|  | 166 | +    NoDCall, | 
|  | 167 | +    NoGasLeft, | 
|  | 168 | +    NoSender, | 
|  | 169 | +} | 
|  | 170 | + | 
|  | 171 | +fn get_syscall_instructions(module: &Module) -> Result<Instructions,SysCallError> { | 
|  | 172 | +    // If any of these three environments are not pulled in from the | 
|  | 173 | +    // environment, we cannot have syscalls. | 
|  | 174 | +    let dcall_index = find_import(module, "env", "dcall").ok_or(SysCallError::NoDCall)?; | 
|  | 175 | +    let gasleft_index = find_import(module, "env", "gasleft").ok_or(SysCallError::NoGasLeft)?; | 
|  | 176 | +    let sender_index = find_import(module, "env", "sender").ok_or(SysCallError::NoSender)?; | 
|  | 177 | +    let syscall_instructions = parity_wasm::elements::Instructions::new(vec![ | 
|  | 178 | +        // Call gas | 
|  | 179 | +        Instruction::Call(gasleft_index), | 
|  | 180 | +        // Call sender | 
|  | 181 | +        Instruction::Call(sender_index), | 
|  | 182 | +        Instruction::GetLocal(0), | 
|  | 183 | +        Instruction::GetLocal(1), | 
|  | 184 | +        Instruction::GetLocal(2), | 
|  | 185 | +        Instruction::GetLocal(3), | 
|  | 186 | +        // Do the delegate call | 
|  | 187 | +        Instruction::Call(dcall_index), | 
|  | 188 | +        // End function | 
|  | 189 | +        Instruction::End, | 
|  | 190 | +        ]); | 
|  | 191 | +    Ok(syscall_instructions) | 
|  | 192 | +} | 
0 commit comments