Skip to content

Commit 2fbd8df

Browse files
committed
test(e2e): refactor to e2e_tests.rs file
1 parent cabfcd9 commit 2fbd8df

File tree

2 files changed

+255
-266
lines changed

2 files changed

+255
-266
lines changed
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
use crate::flipper::FlipperRef;
2+
use ink::{
3+
env::DefaultEnvironment,
4+
primitives::AccountId,
5+
H160,
6+
};
7+
use ink_e2e::{
8+
subxt::tx::Signer,
9+
subxt_signer,
10+
PolkadotConfig,
11+
Weight,
12+
};
13+
use std::process::{
14+
Command,
15+
Stdio,
16+
};
17+
18+
const DEFAULT_GAS: Weight = Weight::from_parts(100_000_000_000, 1024 * 1024);
19+
const DEFAULT_STORAGE_DEPOSIT_LIMIT: u128 = 10_000_000_000_000;
20+
type E2EResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
21+
22+
#[ink_e2e::test]
23+
async fn solidity_calls_ink_works<Client: E2EBackend>(
24+
mut client: Client,
25+
) -> E2EResult<()> {
26+
let constructor = FlipperRef::new(false);
27+
let params = constructor
28+
.endowment(0u32.into())
29+
.code_hash(ink::primitives::H256::zero())
30+
.salt_bytes(None)
31+
.params();
32+
let exec_input = params.exec_input();
33+
34+
// fund alith
35+
let alith = eth_account(subxt_signer::eth::dev::alith());
36+
client
37+
.api
38+
.try_transfer_balance(&ink_e2e::alice(), alith.0.into(), 1_000_000_000_000_000)
39+
.await?;
40+
41+
let signer = ink_e2e::alice();
42+
43+
// deploy ink! flipper (RLP encoded)
44+
client.api.map_account(&signer).await;
45+
let res = client
46+
.exec_instantiate(
47+
&signer,
48+
client.contracts.load_code("flipper"),
49+
ink::scale::Encode::encode(&exec_input),
50+
0,
51+
DEFAULT_GAS.into(),
52+
DEFAULT_STORAGE_DEPOSIT_LIMIT,
53+
)
54+
.await?;
55+
56+
let ink_addr = res.addr;
57+
58+
let get_selector = keccak_selector(b"get");
59+
let value: bool = call_ink(&mut client, ink_addr, get_selector.clone()).await;
60+
assert_eq!(value, false);
61+
62+
let sol_dir = "./sol".to_string();
63+
64+
let mut sol_handler = SolidityHandler::new(sol_dir, client.url().to_string());
65+
sol_handler.start_eth_rpc()?;
66+
sol_handler.setup()?;
67+
let sol_addr = sol_handler.deploy(ink_addr)?;
68+
let _ = sol_handler.call(sol_addr.clone(), "callFlip")?;
69+
70+
// check if flip worked
71+
let value: bool = call_ink(&mut client, ink_addr, get_selector.clone()).await;
72+
assert_eq!(value, true);
73+
74+
let _ = sol_handler.call(sol_addr.clone(), "callFlip2")?;
75+
76+
let value: bool = call_ink(&mut client, ink_addr, get_selector).await;
77+
assert_eq!(value, false);
78+
79+
// TODO: will not succeed until ink! can return RLP encoded data.
80+
let output = sol_handler.call(sol_addr, "callGet")?;
81+
assert_eq!(output, Some("true".to_string()));
82+
83+
Ok(())
84+
}
85+
86+
async fn call_ink<Ret>(
87+
client: &mut ink_e2e::Client<PolkadotConfig, DefaultEnvironment>,
88+
ink_addr: H160,
89+
data_rlp: Vec<u8>,
90+
) -> Ret
91+
where
92+
Ret: ink::rlp::Decodable,
93+
{
94+
let signer = ink_e2e::alice();
95+
let exec_result = client
96+
.api
97+
.call_dry_run(
98+
<ink_e2e::Keypair as Signer<PolkadotConfig>>::account_id(&signer),
99+
ink_addr,
100+
data_rlp,
101+
0,
102+
0,
103+
)
104+
.await;
105+
106+
ink::rlp::Decodable::decode(&mut &exec_result.result.unwrap().data[..])
107+
.expect("decode failed")
108+
}
109+
110+
struct SolidityHandler {
111+
sol_dir: String,
112+
node_url: String,
113+
eth_rpc_process_id: Option<u32>,
114+
}
115+
116+
impl SolidityHandler {
117+
fn new(sol_dir: String, node_url: String) -> Self {
118+
Self {
119+
sol_dir,
120+
node_url,
121+
eth_rpc_process_id: None,
122+
}
123+
}
124+
125+
fn start_eth_rpc(&mut self) -> Result<(), Box<dyn std::error::Error>> {
126+
let eth_rpc = Command::new("eth-rpc")
127+
.arg("--dev")
128+
.arg("--node-rpc-url")
129+
.arg(&self.node_url)
130+
.spawn()?;
131+
self.eth_rpc_process_id = Some(eth_rpc.id());
132+
Ok(())
133+
}
134+
135+
fn stop_eth_rpc(&mut self) -> Result<(), Box<dyn std::error::Error>> {
136+
if self.eth_rpc_process_id.is_none() {
137+
return Ok(());
138+
}
139+
Command::new("kill")
140+
.arg("-9")
141+
.arg(&self.eth_rpc_process_id.unwrap().to_string())
142+
.spawn()?;
143+
Ok(())
144+
}
145+
146+
fn setup(&mut self) -> Result<(), Box<dyn std::error::Error>> {
147+
let install_status = Command::new("npm")
148+
.current_dir(&self.sol_dir)
149+
.arg("install")
150+
.arg("--save-dev")
151+
.status()?;
152+
assert!(install_status.success(), "npm install failed");
153+
154+
let compile_status = Command::new("npx")
155+
.current_dir(&self.sol_dir)
156+
.arg("hardhat")
157+
.arg("compile")
158+
.arg("--config")
159+
.arg("hardhat.config.js")
160+
.stdout(Stdio::inherit())
161+
.stderr(Stdio::inherit())
162+
.status()?;
163+
assert!(compile_status.success(), "hardhat compilation failed");
164+
165+
Ok(())
166+
}
167+
168+
fn deploy(&self, ink_addr: H160) -> Result<String, Box<dyn std::error::Error>> {
169+
let deploy_process = Command::new("npx")
170+
.current_dir(&self.sol_dir)
171+
.arg("hardhat")
172+
.arg("run")
173+
.arg("01-deploy.js")
174+
.arg("--network")
175+
.arg("localhost")
176+
.arg("--no-compile")
177+
.arg("--config")
178+
.arg("hardhat.config.js")
179+
.env("INK_ADDRESS", format!("{:?}", ink_addr))
180+
.stdout(Stdio::piped()) // capture stdout
181+
.stderr(Stdio::inherit()) // print stderr
182+
.spawn()?;
183+
let output = deploy_process.wait_with_output()?;
184+
assert!(output.status.success(), "solidity deployment failed");
185+
186+
Ok(String::from_utf8(output.stdout)?
187+
.lines()
188+
.last()
189+
.ok_or("Failed to retrieve contract address")?
190+
.trim()
191+
.to_string())
192+
}
193+
194+
fn call(
195+
&self,
196+
sol_addr: String,
197+
message: &str,
198+
) -> Result<Option<String>, Box<dyn std::error::Error>> {
199+
let call_process = Command::new("npx")
200+
.current_dir(&self.sol_dir)
201+
.arg("hardhat")
202+
.arg("run")
203+
.arg("02-call-flip.js")
204+
.arg("--network")
205+
.arg("localhost")
206+
.arg("--no-compile")
207+
.arg("--config")
208+
.arg("hardhat.config.js")
209+
.env("SOL_ADDRESS", sol_addr)
210+
.env("MESSAGE", message)
211+
.stdout(Stdio::piped())
212+
.stderr(Stdio::inherit())
213+
.spawn()?;
214+
let output = call_process.wait_with_output()?;
215+
assert!(
216+
output.status.success(),
217+
"solidity call failed on {}",
218+
message
219+
);
220+
Ok(String::from_utf8(output.stdout)
221+
.ok()
222+
.and_then(|s| Some(s.lines().last()?.to_string()))
223+
.map(|s| s.trim().to_string()))
224+
}
225+
}
226+
227+
impl Drop for SolidityHandler {
228+
fn drop(&mut self) {
229+
self.stop_eth_rpc().unwrap();
230+
}
231+
}
232+
233+
// borrowed from: https://github.com/paritytech/polkadot-sdk/blob/master/substrate/bin/node/cli/src/chain_spec.rs#L427
234+
fn eth_account(from: subxt_signer::eth::Keypair) -> AccountId {
235+
let mut account_id = AccountId::from([0xEE; 32]);
236+
<AccountId as AsMut<[u8; 32]>>::as_mut(&mut account_id)[..20]
237+
.copy_from_slice(&from.public_key().to_account_id().as_ref());
238+
account_id
239+
}
240+
241+
fn keccak_selector(input: &[u8]) -> Vec<u8> {
242+
let mut output = [0; 32];
243+
use sha3::{
244+
digest::generic_array::GenericArray,
245+
Digest as _,
246+
};
247+
let mut hasher = sha3::Keccak256::new();
248+
hasher.update(input);
249+
hasher.finalize_into(<&mut GenericArray<u8, _>>::from(&mut output[..]));
250+
251+
vec![output[0], output[1], output[2], output[3]]
252+
}

0 commit comments

Comments
 (0)