Skip to content

Commit 3ca7c65

Browse files
add redirect for evm canister (#77)
* add redirect for evm canister * add methods * fix * fix * bump version * fix * fix * fix * checkpoint * fix
1 parent f3bcbf1 commit 3ca7c65

File tree

10 files changed

+226
-140
lines changed

10 files changed

+226
-140
lines changed

Cargo.lock

Lines changed: 92 additions & 86 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ic-wasm"
3-
version = "0.9.0"
3+
version = "0.9.1"
44
authors = ["DFINITY Stiftung"]
55
edition = "2021"
66
description = "A library for performing Wasm transformations specific to canisters running on the Internet Computer"

src/limit_resource.rs

Lines changed: 100 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use candid::Principal;
12
use walrus::ir::*;
23
use walrus::*;
34

@@ -295,6 +296,58 @@ fn make_grow64_func(m: &mut Module, limit: i64) -> FunctionId {
295296
);
296297
builder.finish(vec![requested], &mut m.funcs)
297298
}
299+
fn check_list(
300+
memory: MemoryId,
301+
checks: &mut InstrSeqBuilder,
302+
no_redirect: LocalId,
303+
size: LocalId,
304+
src: LocalId,
305+
is_rename: Option<LocalId>,
306+
list: &Vec<&[u8]>,
307+
) {
308+
let checks_id = checks.id();
309+
for bytes in list {
310+
checks.block(None, |list_check| {
311+
let list_check_id = list_check.id();
312+
// Check the length
313+
list_check
314+
.local_get(size)
315+
.i32_const(bytes.len() as i32)
316+
.binop(BinaryOp::I32Ne)
317+
.br_if(list_check_id);
318+
// Load bytes at src onto the stack
319+
for i in 0..bytes.len() {
320+
list_check.local_get(src).load(
321+
memory,
322+
LoadKind::I32_8 {
323+
kind: ExtendedLoad::ZeroExtend,
324+
},
325+
MemArg {
326+
offset: i as u32,
327+
align: 1,
328+
},
329+
);
330+
}
331+
for byte in bytes.iter().rev() {
332+
list_check
333+
.i32_const(*byte as i32)
334+
.binop(BinaryOp::I32Ne)
335+
.br_if(list_check_id);
336+
}
337+
// names were equal, so skip all remaining checks and redirect
338+
if let Some(is_rename) = is_rename {
339+
if bytes == b"http_request" {
340+
list_check.i32_const(1).local_set(is_rename);
341+
} else {
342+
list_check.i32_const(0).local_set(is_rename);
343+
}
344+
}
345+
list_check.i32_const(0).local_set(no_redirect).br(checks_id);
346+
});
347+
}
348+
// None matched
349+
checks.i32_const(1).local_set(no_redirect);
350+
}
298351
fn make_redirect_call_new(m: &mut Module, redirect_id: &[u8]) -> FunctionId {
299352
// Specify the same args as `call_new` so that WASM will correctly check mismatching args
300353
let callee_src = m.locals.add(ValType::I32);
@@ -316,9 +369,14 @@ fn make_redirect_call_new(m: &mut Module, redirect_id: &[u8]) -> FunctionId {
316369
for _ in 0..redirect_id.len() {
317370
memory_backup.push(m.locals.add(ValType::I32));
318371
}
372+
let redirect_canisters = [
373+
Principal::from_slice(&[]),
374+
Principal::from_text("7hfb6-caaaa-aaaar-qadga-cai").unwrap(),
375+
];
319376

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

340408
let mut builder = FunctionBuilder::new(
@@ -356,57 +424,37 @@ fn make_redirect_call_new(m: &mut Module, redirect_id: &[u8]) -> FunctionId {
356424
.func_body()
357425
.block(None, |checks| {
358426
let checks_id = checks.id();
359-
360-
// Check that callee address is empty
427+
// Check if callee address is from redirect_canisters
361428
checks
362-
.local_get(callee_size)
363-
.i32_const(0)
364-
.binop(BinaryOp::I32Ne)
365-
.local_tee(no_redirect)
429+
.block(None, |id_check| {
430+
check_list(
431+
memory,
432+
id_check,
433+
no_redirect,
434+
callee_size,
435+
callee_src,
436+
None,
437+
&redirect_canisters
438+
.iter()
439+
.map(|p| p.as_slice())
440+
.collect::<Vec<_>>(),
441+
);
442+
})
443+
.local_get(no_redirect)
366444
.br_if(checks_id);
367-
368-
// Check if the function name is any of the ones to be redirected
369-
for func_name in controller_function_names {
370-
checks.block(None, |name_check| {
371-
let name_check_id = name_check.id();
372-
name_check
373-
// Check that name_size is the same length as the function name
374-
.local_get(name_size)
375-
.i32_const(func_name.len() as i32)
376-
.binop(BinaryOp::I32Ne)
377-
.br_if(name_check_id);
378-
379-
// Load the string at name_src onto the stack and compare it to the function name
380-
for i in 0..func_name.len() {
381-
name_check.local_get(name_src).load(
382-
memory,
383-
LoadKind::I32_8 {
384-
kind: ExtendedLoad::SignExtend,
385-
},
386-
MemArg {
387-
offset: i as u32,
388-
align: 1,
389-
},
390-
);
391-
}
392-
for c in func_name.chars().rev() {
393-
name_check
394-
.i32_const(c as i32)
395-
.binop(BinaryOp::I32Ne)
396-
.br_if(name_check_id);
397-
}
398-
// Function names were equal, so skip all remaining checks and redirect
399-
if func_name == "http_request" {
400-
name_check.i32_const(1).local_set(is_rename);
401-
} else {
402-
name_check.i32_const(0).local_set(is_rename);
403-
}
404-
name_check.i32_const(0).local_set(no_redirect).br(checks_id);
405-
});
406-
}
407-
408-
// None of the function names matched
409-
checks.i32_const(1).local_set(no_redirect);
445+
// Callee address matches, check method name is in the list
446+
check_list(
447+
memory,
448+
checks,
449+
no_redirect,
450+
name_size,
451+
name_src,
452+
Some(is_rename),
453+
&controller_function_names
454+
.iter()
455+
.map(|s| s.as_bytes())
456+
.collect::<Vec<_>>(),
457+
);
410458
})
411459
.local_get(no_redirect)
412460
.if_else(
@@ -432,7 +480,7 @@ fn make_redirect_call_new(m: &mut Module, redirect_id: &[u8]) -> FunctionId {
432480
.load(
433481
memory,
434482
LoadKind::I32_8 {
435-
kind: ExtendedLoad::SignExtend,
483+
kind: ExtendedLoad::ZeroExtend,
436484
},
437485
MemArg {
438486
offset: 0,

tests/deployable.ic-repl.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,18 @@ function check_profiling(S, cycles, len) {
9696
assert _[0].size() == (sub(len,1) : nat);
9797
null
9898
};
99+
function evm_redirect(wasm) {
100+
let S = install(wasm);
101+
fail call S.request();
102+
assert _ ~= "zz73r-nyaaa-aabbb-aaaca-cai not found";
103+
fail call S.requestCost();
104+
assert _ ~= "7hfb6-caaaa-aaaar-qadga-cai not found";
105+
fail call S.non_evm_request();
106+
assert _ ~= "cpmcr-yeaaa-aaaaa-qaala-cai not found";
107+
S
108+
};
109+
110+
evm_redirect(file("ok/evm-redirect.wasm"));
99111

100112
let S = counter(file("ok/motoko-instrument.wasm"));
101113
check_profiling(S, 21571, 78);

tests/evm.mo

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
actor {
2+
let evm : actor { request: shared () -> async (); requestCost: shared () -> async () } = actor "7hfb6-caaaa-aaaar-qadga-cai";
3+
let non_evm : actor { request: shared () -> async (); requestCost: shared () -> async () } = actor "cpmcr-yeaaa-aaaaa-qaala-cai";
4+
public func requestCost() : async () {
5+
await evm.requestCost();
6+
};
7+
public func request() : async () {
8+
await evm.request();
9+
};
10+
public func non_evm_request() : async () {
11+
await non_evm.request();
12+
};
13+
}
14+

tests/evm.wasm

214 KB
Binary file not shown.

tests/ok/classes-nop-redirect.wasm

1.87 KB
Binary file not shown.

tests/ok/classes-redirect.wasm

1.87 KB
Binary file not shown.

tests/ok/evm-redirect.wasm

187 KB
Binary file not shown.

tests/tests.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ fn shrink() {
123123
.success();
124124
assert_wasm("classes-shrink.wasm");
125125
}
126-
127126
#[test]
128127
fn optimize() {
129128
let expected_metadata = r#"icp:public candid:service
@@ -213,6 +212,13 @@ fn resource() {
213212
.assert()
214213
.success();
215214
assert_wasm("classes-nop-redirect.wasm");
215+
wasm_input("evm.wasm", true)
216+
.arg("resource")
217+
.arg("--playground-backend-redirect")
218+
.arg(test_canister_id)
219+
.assert()
220+
.success();
221+
assert_wasm("evm-redirect.wasm");
216222
}
217223

218224
#[test]

0 commit comments

Comments
 (0)