Skip to content

Commit

Permalink
Add Value module to support path and blob inside Candid value (#4)
Browse files Browse the repository at this point in the history
* add Value module to support path and blob inside Candid value

* easier than I thought :)

* encode command

* encode value

* fix

Co-authored-by: chenyan-dfinity <[email protected]>
  • Loading branch information
chenyan2002 and chenyan-dfinity authored May 2, 2021
1 parent 8519917 commit 733a806
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 105 deletions.
50 changes: 38 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,26 @@ ic-repl --replica [local|ic|url] --config <dhall config> [script file]

```
<command> :=
| import <id> = <text> [ : <text> ] (canister URI with optional did file)
| export <text> (filename)
| load <text> (filename)
| config <text> (dhall config)
| call <name> . <name> ( <val>,* )
| let <id> = <val>
| show <val>
| assert <val> <binop> <val>
| identity <id>
| import <id> = <text> [ : <text> ] // bind canister URI to <id>, with optional did file
| call <name> . <name> ( <val>,* ) // call a canister method with candid arguments
| encode <name> . <name> ( <val>,* ) // encode candid arguments with respect to a canister method signature
| export <text> // export command history to a file that can be run in ic-repl as a script
| load <text> // load and run a script file
| config <text> // set config for random value generator in dhall format
| let <id> = <val> // bind <val> to a variable <id>
| show <val> // show the value of <val>
| assert <val> <binop> <val> // assertion
| identity <id> // switch to identity <id> (create a new one if doesn't exist)
<var> := <id> | _
<val> := <candid val> | <var> (. <id>)* | file <text>
<binop> := == | ~= | !=
<var> := <id> | _ (previous call result is bind to `_`)
<val> :=
| <candid val> | <var> (. <id>)*
| file <text> // load external file as a blob value
| encode ( <val),* ) // encode candid arguments as a blob value
<binop> :=
| == // structural equality
| ~= // equal under candid subtyping
| != // not equal
```

## Example
Expand All @@ -38,6 +45,23 @@ call "rrkah-fqaaa-aaaaa-aaaaq-cai".greet("test");
assert _ == result;
```

install.sh
```
#!/usr/bin/ic-repl -r ic
call "aaaaa-aa".provisional_create_canister_with_cycles(record { settings: null; amount: null });
let id = _;
call "aaaaa-aa".install_code(
record {
arg = encode ();
wasm_module = file "your_wasm_file.wasm";
mode = variant { install };
canister_id = id.canister_id; // TODO
},
);
call "aaaaa-aa".canister_status(id);
call id.canister_id.greet("test");
```

## Notes for Rust canisters

`ic-repl` relies on the `__get_candid_interface_tmp_hack` canister method to fetch the Candid interface. The default
Expand All @@ -58,6 +82,8 @@ If you are writing your own `.did` file, you can also supply the did file via th

## Issues

* Acess to service init type
* `IDLValue::Blob` for efficient blob serialization
* Autocompletion within Candid value
* Robust support for `~=`, requires inferring principal types
* Value projection
Expand Down
81 changes: 28 additions & 53 deletions src/command.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use super::error::pretty_parse;
use super::helper::{did_to_canister_info, MyHelper, NameEnv};
use super::token::{ParserError, Spanned, Tokenizer};
use super::value::Value;
use anyhow::{anyhow, Context};
use candid::{
parser::configs::Configs, parser::value::IDLValue, types::Function, IDLArgs, Principal, TypeEnv,
Expand All @@ -11,44 +12,15 @@ use std::path::{Path, PathBuf};
use std::time::Instant;
use terminal_size::{terminal_size, Width};

#[derive(Debug, Clone)]
pub enum Value {
Candid(IDLValue),
Path(Vec<String>),
Blob(String),
}
impl Value {
fn get<'a>(&'a self, helper: &'a MyHelper) -> anyhow::Result<IDLValue> {
Ok(match self {
Value::Candid(v) => v.clone(),
Value::Path(vs) => helper
.env
.0
.get(&vs[0])
.ok_or_else(|| anyhow!("Undefined variable {}", vs[0]))?
.clone(),
Value::Blob(file) => {
let path = resolve_path(&helper.base_path, PathBuf::from(file));
let blob: Vec<IDLValue> = std::fs::read(&path)
.with_context(|| format!("Cannot read {:?}", path))?
.into_iter()
.map(IDLValue::Nat8)
.collect();
IDLValue::Vec(blob)
}
})
}
}

#[derive(Debug, Clone)]
pub struct Commands(pub Vec<Command>);

#[derive(Debug, Clone)]
pub enum Command {
Call {
canister: Spanned<String>,
method: String,
args: Vec<Value>,
encode_only: bool,
},
Config(String),
Show(Value),
Expand All @@ -67,12 +39,13 @@ pub enum BinOp {
}

impl Command {
pub fn run(&self, helper: &mut MyHelper) -> anyhow::Result<()> {
pub fn run(self, helper: &mut MyHelper) -> anyhow::Result<()> {
match self {
Command::Call {
canister,
method,
args,
encode_only,
} => {
let try_id = Principal::from_text(&canister.value);
let canister_id = match try_id {
Expand All @@ -88,13 +61,20 @@ impl Command {
let info = map.get(&agent, canister_id)?;
let func = info
.methods
.get(method)
.get(&method)
.ok_or_else(|| anyhow!("no method {}", method))?;
let mut values = Vec::new();
for arg in args.iter() {
values.push(arg.get(&helper)?);
for arg in args.into_iter() {
values.push(arg.eval(&helper)?);
}
let args = IDLArgs { args: values };
if encode_only {
let bytes = args.to_bytes_with_types(&info.env, &func.args)?;
let res = IDLValue::Vec(bytes.into_iter().map(IDLValue::Nat8).collect());
println!("{}", res);
helper.env.0.insert("_".to_string(), res);
return Ok(());
}
let time = Instant::now();
let res = call(&agent, canister_id, &method, &args, &info.env, &func)?;
let duration = time.elapsed();
Expand All @@ -111,29 +91,26 @@ impl Command {
}
}
Command::Import(id, canister_id, did) => {
if let Some(did) = did {
if let Some(did) = &did {
let path = resolve_path(&helper.base_path, PathBuf::from(did));
let src = std::fs::read_to_string(&path)
.with_context(|| format!("Cannot read {:?}", path))?;
let info = did_to_canister_info(did, &src)?;
let info = did_to_canister_info(&did, &src)?;
helper
.canister_map
.borrow_mut()
.0
.insert(canister_id.clone(), info);
}
helper
.canister_env
.0
.insert(id.to_string(), canister_id.clone());
helper.canister_env.0.insert(id, canister_id);
}
Command::Let(id, val) => {
let v = val.get(&helper)?;
helper.env.0.insert(id.to_string(), v);
let v = val.eval(&helper)?;
helper.env.0.insert(id, v);
}
Command::Assert(op, left, right) => {
let left = left.get(&helper)?;
let right = right.get(&helper)?;
let left = left.eval(&helper)?;
let right = right.eval(&helper)?;
match op {
BinOp::Equal => assert_eq!(left, right),
BinOp::SubEqual => {
Expand All @@ -153,12 +130,12 @@ impl Command {
}
Command::Config(conf) => helper.config = Configs::from_dhall(&conf)?,
Command::Show(val) => {
let v = val.get(&helper)?;
let v = val.eval(&helper)?;
println!("{}", v);
}
Command::Identity(id) => {
use ic_agent::Identity;
let keypair = if let Some(keypair) = helper.identity_map.0.get(id) {
let keypair = if let Some(keypair) = helper.identity_map.0.get(&id) {
keypair.to_vec()
} else {
let rng = ring::rand::SystemRandom::new();
Expand Down Expand Up @@ -191,10 +168,7 @@ impl Command {
}
helper.agent = agent;
helper.current_identity = id.to_string();
helper
.env
.0
.insert(id.to_string(), IDLValue::Principal(sender));
helper.env.0.insert(id, IDLValue::Principal(sender));
}
Command::Export(file) => {
use std::io::{BufWriter, Write};
Expand All @@ -207,7 +181,7 @@ impl Command {
Command::Load(file) => {
// TODO check for infinite loop
let old_base = helper.base_path.clone();
let path = resolve_path(&old_base, PathBuf::from(file));
let path = resolve_path(&old_base, PathBuf::from(&file));
let mut script = std::fs::read_to_string(&path)
.with_context(|| format!("Cannot read {:?}", path))?;
if script.starts_with("#!") {
Expand All @@ -216,7 +190,7 @@ impl Command {
}
let cmds = pretty_parse::<Commands>(&file, &script)?;
helper.base_path = path.parent().unwrap().to_path_buf();
for cmd in cmds.0.iter() {
for cmd in cmds.0.into_iter() {
println!("> {:?}", cmd);
cmd.run(helper)?;
}
Expand Down Expand Up @@ -286,6 +260,7 @@ pub fn extract_canister(
canister,
method,
args,
..
} => {
let try_id = Principal::from_text(&canister.value);
let canister_id = match try_id {
Expand All @@ -298,7 +273,7 @@ pub fn extract_canister(
}
}

fn resolve_path(base: &Path, file: PathBuf) -> PathBuf {
pub fn resolve_path(base: &Path, file: PathBuf) -> PathBuf {
if file.is_absolute() {
file
} else {
Expand Down
Loading

0 comments on commit 733a806

Please sign in to comment.