Skip to content

Commit

Permalink
add redirect for evm canister (#77)
Browse files Browse the repository at this point in the history
* add redirect for evm canister

* add methods

* fix

* fix

* bump version

* fix

* fix

* fix

* checkpoint

* fix
  • Loading branch information
chenyan-dfinity authored Nov 18, 2024
1 parent f3bcbf1 commit 3ca7c65
Show file tree
Hide file tree
Showing 10 changed files with 226 additions and 140 deletions.
178 changes: 92 additions & 86 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ic-wasm"
version = "0.9.0"
version = "0.9.1"
authors = ["DFINITY Stiftung"]
edition = "2021"
description = "A library for performing Wasm transformations specific to canisters running on the Internet Computer"
Expand Down
152 changes: 100 additions & 52 deletions src/limit_resource.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use candid::Principal;
use walrus::ir::*;
use walrus::*;

Expand Down Expand Up @@ -295,6 +296,58 @@ fn make_grow64_func(m: &mut Module, limit: i64) -> FunctionId {
);
builder.finish(vec![requested], &mut m.funcs)
}
fn check_list(
memory: MemoryId,
checks: &mut InstrSeqBuilder,
no_redirect: LocalId,
size: LocalId,
src: LocalId,
is_rename: Option<LocalId>,
list: &Vec<&[u8]>,
) {
let checks_id = checks.id();
for bytes in list {
checks.block(None, |list_check| {
let list_check_id = list_check.id();
// Check the length
list_check
.local_get(size)
.i32_const(bytes.len() as i32)
.binop(BinaryOp::I32Ne)
.br_if(list_check_id);
// Load bytes at src onto the stack
for i in 0..bytes.len() {
list_check.local_get(src).load(
memory,
LoadKind::I32_8 {
kind: ExtendedLoad::ZeroExtend,
},
MemArg {
offset: i as u32,
align: 1,
},
);
}
for byte in bytes.iter().rev() {
list_check
.i32_const(*byte as i32)
.binop(BinaryOp::I32Ne)
.br_if(list_check_id);
}
// names were equal, so skip all remaining checks and redirect
if let Some(is_rename) = is_rename {
if bytes == b"http_request" {
list_check.i32_const(1).local_set(is_rename);
} else {
list_check.i32_const(0).local_set(is_rename);
}
}
list_check.i32_const(0).local_set(no_redirect).br(checks_id);
});
}
// None matched
checks.i32_const(1).local_set(no_redirect);
}
fn make_redirect_call_new(m: &mut Module, redirect_id: &[u8]) -> FunctionId {
// Specify the same args as `call_new` so that WASM will correctly check mismatching args
let callee_src = m.locals.add(ValType::I32);
Expand All @@ -316,9 +369,14 @@ fn make_redirect_call_new(m: &mut Module, redirect_id: &[u8]) -> FunctionId {
for _ in 0..redirect_id.len() {
memory_backup.push(m.locals.add(ValType::I32));
}
let redirect_canisters = [
Principal::from_slice(&[]),
Principal::from_text("7hfb6-caaaa-aaaar-qadga-cai").unwrap(),
];

// All management canister functions that require controller permissions
// The following wasm code assumes that this list is non-empty
// All functions that require controller permissions or cycles.
// For simplicity, We mingle all canister methods in a single list.
// Method names shouldn't overlap.
let controller_function_names = [
"create_canister",
"update_settings",
Expand All @@ -333,8 +391,18 @@ fn make_redirect_call_new(m: &mut Module, redirect_id: &[u8]) -> FunctionId {
"load_canister_snapshot",
"delete_canister_snapshot",
// These functions doesn't require controller permissions, but needs cycles
"sign_with_ecdsa",
"http_request", // Will be renamed to "_ttp_request", because the name conflicts with the http serving endpoint.
"_ttp_request", // need to redirect renamed function as well, because the second time we see this function, it's already renamed in memory
// methods from evm canister
"eth_call",
"eth_feeHistory",
"eth_getBlockByNumber",
"eth_getLogs",
"eth_getTransactionCount",
"eth_getTransactionReceipt",
"eth_sendRawTransaction",
"request",
];

let mut builder = FunctionBuilder::new(
Expand All @@ -356,57 +424,37 @@ fn make_redirect_call_new(m: &mut Module, redirect_id: &[u8]) -> FunctionId {
.func_body()
.block(None, |checks| {
let checks_id = checks.id();

// Check that callee address is empty
// Check if callee address is from redirect_canisters
checks
.local_get(callee_size)
.i32_const(0)
.binop(BinaryOp::I32Ne)
.local_tee(no_redirect)
.block(None, |id_check| {
check_list(
memory,
id_check,
no_redirect,
callee_size,
callee_src,
None,
&redirect_canisters
.iter()
.map(|p| p.as_slice())
.collect::<Vec<_>>(),
);
})
.local_get(no_redirect)
.br_if(checks_id);

// Check if the function name is any of the ones to be redirected
for func_name in controller_function_names {
checks.block(None, |name_check| {
let name_check_id = name_check.id();
name_check
// Check that name_size is the same length as the function name
.local_get(name_size)
.i32_const(func_name.len() as i32)
.binop(BinaryOp::I32Ne)
.br_if(name_check_id);

// Load the string at name_src onto the stack and compare it to the function name
for i in 0..func_name.len() {
name_check.local_get(name_src).load(
memory,
LoadKind::I32_8 {
kind: ExtendedLoad::SignExtend,
},
MemArg {
offset: i as u32,
align: 1,
},
);
}
for c in func_name.chars().rev() {
name_check
.i32_const(c as i32)
.binop(BinaryOp::I32Ne)
.br_if(name_check_id);
}
// Function names were equal, so skip all remaining checks and redirect
if func_name == "http_request" {
name_check.i32_const(1).local_set(is_rename);
} else {
name_check.i32_const(0).local_set(is_rename);
}
name_check.i32_const(0).local_set(no_redirect).br(checks_id);
});
}

// None of the function names matched
checks.i32_const(1).local_set(no_redirect);
// Callee address matches, check method name is in the list
check_list(
memory,
checks,
no_redirect,
name_size,
name_src,
Some(is_rename),
&controller_function_names
.iter()
.map(|s| s.as_bytes())
.collect::<Vec<_>>(),
);
})
.local_get(no_redirect)
.if_else(
Expand All @@ -432,7 +480,7 @@ fn make_redirect_call_new(m: &mut Module, redirect_id: &[u8]) -> FunctionId {
.load(
memory,
LoadKind::I32_8 {
kind: ExtendedLoad::SignExtend,
kind: ExtendedLoad::ZeroExtend,
},
MemArg {
offset: 0,
Expand Down
12 changes: 12 additions & 0 deletions tests/deployable.ic-repl.sh
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,18 @@ function check_profiling(S, cycles, len) {
assert _[0].size() == (sub(len,1) : nat);
null
};
function evm_redirect(wasm) {
let S = install(wasm);
fail call S.request();
assert _ ~= "zz73r-nyaaa-aabbb-aaaca-cai not found";
fail call S.requestCost();
assert _ ~= "7hfb6-caaaa-aaaar-qadga-cai not found";
fail call S.non_evm_request();
assert _ ~= "cpmcr-yeaaa-aaaaa-qaala-cai not found";
S
};

evm_redirect(file("ok/evm-redirect.wasm"));

let S = counter(file("ok/motoko-instrument.wasm"));
check_profiling(S, 21571, 78);
Expand Down
14 changes: 14 additions & 0 deletions tests/evm.mo
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
actor {
let evm : actor { request: shared () -> async (); requestCost: shared () -> async () } = actor "7hfb6-caaaa-aaaar-qadga-cai";
let non_evm : actor { request: shared () -> async (); requestCost: shared () -> async () } = actor "cpmcr-yeaaa-aaaaa-qaala-cai";
public func requestCost() : async () {
await evm.requestCost();
};
public func request() : async () {
await evm.request();
};
public func non_evm_request() : async () {
await non_evm.request();
};
}

Binary file added tests/evm.wasm
Binary file not shown.
Binary file modified tests/ok/classes-nop-redirect.wasm
Binary file not shown.
Binary file modified tests/ok/classes-redirect.wasm
Binary file not shown.
Binary file added tests/ok/evm-redirect.wasm
Binary file not shown.
8 changes: 7 additions & 1 deletion tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ fn shrink() {
.success();
assert_wasm("classes-shrink.wasm");
}

#[test]
fn optimize() {
let expected_metadata = r#"icp:public candid:service
Expand Down Expand Up @@ -213,6 +212,13 @@ fn resource() {
.assert()
.success();
assert_wasm("classes-nop-redirect.wasm");
wasm_input("evm.wasm", true)
.arg("resource")
.arg("--playground-backend-redirect")
.arg(test_canister_id)
.assert()
.success();
assert_wasm("evm-redirect.wasm");
}

#[test]
Expand Down

0 comments on commit 3ca7c65

Please sign in to comment.