Skip to content

Commit 97e3665

Browse files
Implement the end-to-end test that calls an EVM smart contract. (#3685)
## Motivation The EVM smart contract have the `EvmAbi`. A test is needed to demonstrate on how to call them from Wasm. ## Proposal The following changes are made: * The `EvmQuery` is added to the `linera_base_types`. * The `call-evm-counter` is added, which is a non GraphQL app. * A demonstration of query and mutation is introduced in the end-to-end test. There are some changes to the `linera-sdk`: * The `EvmAbi` should not be protected by a `revm` feature as the fact that we use REVM is incidental. * The `call-evm-counter` calling for the `revm` feature forces the `revm` feature by default which is unfortunate. This is the first example of a query to a smart contract that leads to another `query_application` being created in the end-to-end test. ## Test Plan The introduced test demonstrates the functionality. ## Release Plan Normal release plan. ## Links None.
1 parent 48c3891 commit 97e3665

File tree

14 files changed

+298
-10
lines changed

14 files changed

+298
-10
lines changed

Cargo.lock

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ linera-witty = { version = "0.14.0", path = "./linera-witty" }
250250
linera-witty-macros = { version = "0.14.0", path = "./linera-witty-macros" }
251251

252252
amm = { path = "./examples/amm" }
253+
call-evm-counter = { path = "./examples/call-evm-counter" }
253254
counter = { path = "./examples/counter" }
254255
counter-no-graphql = { path = "./examples/counter-no-graphql" }
255256
crowd-funding = { path = "./examples/crowd-funding" }

examples/Cargo.lock

+12
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ resolver = "2"
33
members = [
44
"amm",
55
"counter",
6+
"call-evm-counter",
67
"counter-no-graphql",
78
"crowd-funding",
89
"ethereum-tracker",
@@ -21,6 +22,7 @@ members = [
2122

2223
[workspace.dependencies]
2324
alloy = { version = "0.9.2", default-features = false }
25+
alloy-sol-types = "0.8.18"
2426
anyhow = "1.0.80"
2527
assert_matches = "1.5.0"
2628
async-graphql = { version = "=7.0.2", default-features = false }

examples/call-evm-counter/Cargo.toml

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[package]
2+
name = "call-evm-counter"
3+
version = "0.1.0"
4+
authors = ["Linera <[email protected]>"]
5+
edition = "2021"
6+
7+
[dependencies]
8+
alloy = { workspace = true, default-features = false, features = [
9+
"rpc-types-eth",
10+
] }
11+
alloy-sol-types.workspace = true
12+
linera-sdk.workspace = true
13+
serde.workspace = true
14+
15+
16+
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
17+
linera-sdk = { workspace = true, features = ["test", "wasmer"] }
18+
tokio = { workspace = true, features = ["rt", "sync"] }
19+
20+
[dev-dependencies]
21+
assert_matches.workspace = true
22+
linera-sdk = { workspace = true, features = ["test"] }
23+
24+
[[bin]]
25+
name = "call_evm_counter_contract"
26+
path = "src/contract.rs"
27+
28+
[[bin]]
29+
name = "call_evm_counter_service"
30+
path = "src/service.rs"
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) Zefchain Labs, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#![cfg_attr(target_arch = "wasm32", no_main)]
5+
6+
use alloy::primitives::U256;
7+
use alloy_sol_types::{sol, SolCall};
8+
use call_evm_counter::{CallCounterAbi, CallCounterOperation};
9+
use linera_sdk::{
10+
abis::evm::EvmAbi,
11+
linera_base_types::{ApplicationId, WithContractAbi},
12+
Contract, ContractRuntime,
13+
};
14+
15+
pub struct CallCounterContract {
16+
runtime: ContractRuntime<Self>,
17+
}
18+
19+
linera_sdk::contract!(CallCounterContract);
20+
21+
impl WithContractAbi for CallCounterContract {
22+
type Abi = CallCounterAbi;
23+
}
24+
25+
impl Contract for CallCounterContract {
26+
type Message = ();
27+
type InstantiationArgument = ();
28+
type Parameters = ApplicationId<EvmAbi>;
29+
30+
async fn load(runtime: ContractRuntime<Self>) -> Self {
31+
CallCounterContract { runtime }
32+
}
33+
34+
async fn instantiate(&mut self, _value: ()) {
35+
// Validate that the application parameters were configured correctly.
36+
self.runtime.application_parameters();
37+
}
38+
39+
async fn execute_operation(&mut self, operation: CallCounterOperation) -> u64 {
40+
let CallCounterOperation::Increment(increment) = operation;
41+
sol! {
42+
function increment(uint64 input);
43+
}
44+
let operation = incrementCall { input: increment };
45+
let operation = operation.abi_encode();
46+
let evm_counter_id = self.runtime.application_parameters();
47+
let result = self
48+
.runtime
49+
.call_application(true, evm_counter_id, &operation);
50+
let arr: [u8; 32] = result.try_into().expect("result should have length 32");
51+
U256::from_be_bytes(arr).to::<u64>()
52+
}
53+
54+
async fn execute_message(&mut self, _message: ()) {
55+
panic!("Counter application doesn't support any cross-chain messages");
56+
}
57+
58+
async fn store(self) {}
59+
}

examples/call-evm-counter/src/lib.rs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) Zefchain Labs, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
/*! ABI of the Counter Example Application that does not use GraphQL */
5+
6+
use linera_sdk::linera_base_types::{ContractAbi, ServiceAbi};
7+
use serde::{Deserialize, Serialize};
8+
9+
pub struct CallCounterAbi;
10+
11+
impl ContractAbi for CallCounterAbi {
12+
type Operation = CallCounterOperation;
13+
type Response = u64;
14+
}
15+
16+
impl ServiceAbi for CallCounterAbi {
17+
type Query = CallCounterRequest;
18+
type QueryResponse = u64;
19+
}
20+
21+
#[derive(Debug, Serialize, Deserialize)]
22+
pub enum CallCounterRequest {
23+
Query,
24+
Increment(u64),
25+
}
26+
27+
#[derive(Debug, Serialize, Deserialize)]
28+
pub enum CallCounterOperation {
29+
Increment(u64),
30+
}
+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) Zefchain Labs, Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
#![cfg_attr(target_arch = "wasm32", no_main)]
5+
6+
use std::sync::Arc;
7+
8+
use alloy::primitives::U256;
9+
use alloy_sol_types::{sol, SolCall};
10+
use call_evm_counter::{CallCounterOperation, CallCounterRequest};
11+
use linera_sdk::{
12+
abis::evm::EvmAbi,
13+
linera_base_types::{ApplicationId, EvmQuery, WithServiceAbi},
14+
Service, ServiceRuntime,
15+
};
16+
17+
pub struct CallCounterService {
18+
runtime: Arc<ServiceRuntime<Self>>,
19+
}
20+
21+
linera_sdk::service!(CallCounterService);
22+
23+
impl WithServiceAbi for CallCounterService {
24+
type Abi = call_evm_counter::CallCounterAbi;
25+
}
26+
27+
impl Service for CallCounterService {
28+
type Parameters = ApplicationId<EvmAbi>;
29+
30+
async fn new(runtime: ServiceRuntime<Self>) -> Self {
31+
CallCounterService {
32+
runtime: Arc::new(runtime),
33+
}
34+
}
35+
36+
async fn handle_query(&self, request: CallCounterRequest) -> u64 {
37+
match request {
38+
CallCounterRequest::Query => {
39+
sol! {
40+
function get_value();
41+
}
42+
let query = get_valueCall {};
43+
let query = query.abi_encode();
44+
let query = EvmQuery::Query(query);
45+
let evm_counter_id = self.runtime.application_parameters();
46+
let result = self.runtime.query_application(evm_counter_id, &query);
47+
let arr: [u8; 32] = result.try_into().expect("result should have length 32");
48+
U256::from_be_bytes(arr).to::<u64>()
49+
}
50+
CallCounterRequest::Increment(value) => {
51+
let operation = CallCounterOperation::Increment(value);
52+
self.runtime.schedule_operation(&operation);
53+
0
54+
}
55+
}
56+
}
57+
}

linera-sdk/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ ignored = ["base64ct"]
2424

2525
[features]
2626
ethereum = ["async-trait", "linera-ethereum"]
27-
revm = ["linera-execution/revm"]
2827
wasmer = [
2928
"linera-core/wasmer",
3029
"linera-execution/wasmer",

linera-sdk/build.rs

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
fn main() {
55
cfg_aliases::cfg_aliases! {
6-
with_revm: { feature = "revm" },
76
with_testing: { any(test, feature = "test") },
87
with_wasm_runtime: { any(feature = "wasmer", feature = "wasmtime") },
98
with_integration_testing: {

linera-sdk/src/abis/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,5 @@
33

44
//! Common ABIs that may have multiple implementations.
55
6-
#[cfg(with_revm)]
76
pub mod evm;
87
pub mod fungible;

linera-sdk/src/linera_base_types.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
//! Types reexported from [`linera_base`].
55
66
pub use linera_base::{
7-
abi::*, crypto::*, data_types::*, identifiers::*, ownership::*, BcsHexParseError,
7+
abi::*, crypto::*, data_types::*, identifiers::*, ownership::*, vm::EvmQuery, BcsHexParseError,
88
};

linera-service/Cargo.toml

+2-6
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,7 @@ version.workspace = true
1414
[features]
1515
ethereum = []
1616
default = ["wasmer", "rocksdb", "storage-service"]
17-
revm = [
18-
"linera-execution/revm",
19-
"linera-storage/revm",
20-
"linera-sdk/revm",
21-
"dep:alloy-sol-types",
22-
]
17+
revm = ["linera-execution/revm", "linera-storage/revm", "dep:alloy-sol-types"]
2318
test = [
2419
"linera-base/test",
2520
"linera-execution/test",
@@ -147,6 +142,7 @@ alloy = { workspace = true, default-features = false, features = [
147142
] }
148143
amm.workspace = true
149144
base64.workspace = true
145+
call-evm-counter.workspace = true
150146
counter.workspace = true
151147
counter-no-graphql.workspace = true
152148
criterion = { workspace = true, features = ["async_tokio"] }

linera-service/tests/linera_net_tests.rs

+93
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,99 @@ async fn test_evm_end_to_end_counter(config: impl LineraNetConfig) -> Result<()>
415415
Ok(())
416416
}
417417

418+
#[cfg(with_revm)]
419+
#[cfg_attr(feature = "storage-service", test_case(LocalNetConfig::new_test(Database::Service, Network::Grpc) ; "storage_test_service_grpc"))]
420+
#[cfg_attr(feature = "scylladb", test_case(LocalNetConfig::new_test(Database::ScyllaDb, Network::Grpc) ; "scylladb_grpc"))]
421+
#[cfg_attr(feature = "dynamodb", test_case(LocalNetConfig::new_test(Database::DynamoDb, Network::Grpc) ; "aws_grpc"))]
422+
#[cfg_attr(feature = "kubernetes", test_case(SharedLocalKubernetesNetTestingConfig::new(Network::Grpc, BuildArg::Build) ; "kubernetes_grpc"))]
423+
#[cfg_attr(feature = "remote-net", test_case(RemoteNetTestingConfig::new(None) ; "remote_net_grpc"))]
424+
#[test_log::test(tokio::test)]
425+
async fn test_wasm_call_evm_end_to_end_counter(config: impl LineraNetConfig) -> Result<()> {
426+
use alloy_sol_types::{sol, SolValue};
427+
use call_evm_counter::{CallCounterAbi, CallCounterRequest};
428+
use linera_execution::test_utils::solidity::{
429+
get_contract_service_paths, get_evm_example_counter,
430+
};
431+
use linera_sdk::abis::evm::EvmAbi;
432+
let _guard = INTEGRATION_TEST_GUARD.lock().await;
433+
tracing::info!("Starting test {}", test_name!());
434+
435+
let (mut net, client) = config.instantiate().await?;
436+
let chain = client.load_wallet()?.default_chain().unwrap();
437+
438+
// Creating the EVM contract
439+
440+
let module = get_evm_example_counter()?;
441+
442+
sol! {
443+
struct ConstructorArgs {
444+
uint64 initial_value;
445+
}
446+
}
447+
448+
let original_counter_value = 35;
449+
let evm_instantiation_argument = ConstructorArgs {
450+
initial_value: original_counter_value,
451+
};
452+
let evm_instantiation_argument = evm_instantiation_argument.abi_encode();
453+
454+
let (evm_contract, evm_service, _dir) = get_contract_service_paths(module)?;
455+
type InstantiationArgument = Vec<u8>;
456+
457+
let evm_application_id = client
458+
.publish_and_create::<EvmAbi, (), InstantiationArgument>(
459+
evm_contract,
460+
evm_service,
461+
VmRuntime::Evm,
462+
&(),
463+
&evm_instantiation_argument,
464+
&[],
465+
None,
466+
)
467+
.await?;
468+
469+
// Creating the WASM contract
470+
471+
let (wasm_contract, wasm_service) = client.build_example("call-evm-counter").await?;
472+
type WasmParameter = ApplicationId<EvmAbi>;
473+
let wasm_application_id = client
474+
.publish_and_create::<CallCounterAbi, WasmParameter, ()>(
475+
wasm_contract,
476+
wasm_service,
477+
VmRuntime::Wasm,
478+
&evm_application_id,
479+
&(),
480+
&[],
481+
None,
482+
)
483+
.await?;
484+
485+
let port = get_node_port().await;
486+
let mut node_service = client.run_node_service(port, ProcessInbox::Skip).await?;
487+
488+
let wasm_application = node_service
489+
.make_application(&chain, &wasm_application_id)
490+
.await?;
491+
492+
let query = CallCounterRequest::Query;
493+
let counter_value = wasm_application.run_json_query(&query).await?;
494+
assert_eq!(counter_value, original_counter_value);
495+
496+
let increment = 5;
497+
let query_increment = CallCounterRequest::Increment(increment);
498+
wasm_application.run_json_query(&query_increment).await?;
499+
500+
let counter_value = wasm_application.run_json_query(&query).await?;
501+
assert_eq!(counter_value, original_counter_value + increment);
502+
503+
node_service.ensure_is_running()?;
504+
505+
net.ensure_is_running().await?;
506+
net.terminate().await?;
507+
508+
Ok(())
509+
}
510+
418511
#[cfg_attr(feature = "storage-service", test_case(LocalNetConfig::new_test(Database::Service, Network::Grpc) ; "storage_test_service_grpc"))]
419512
#[cfg_attr(feature = "scylladb", test_case(LocalNetConfig::new_test(Database::ScyllaDb, Network::Grpc) ; "scylladb_grpc"))]
420513
#[cfg_attr(all(feature = "rocksdb", feature = "scylladb"), test_case(LocalNetConfig::new_test(Database::DualRocksDbScyllaDb, Network::Grpc) ; "dualrocksdbscylladb_grpc"))]

0 commit comments

Comments
 (0)