|
| 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