Skip to main content

forest/dev/subcommands/tests_cmd/
wallet.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use super::helpers::*;
5use libtest_mimic::{Arguments, Trial};
6
7/// Wallet integration tests
8#[derive(Debug, clap::Args)]
9pub struct WalletTestCommand {}
10
11impl WalletTestCommand {
12    pub async fn run(self) -> anyhow::Result<()> {
13        let args = Arguments {
14            test_threads: Some(1),
15            ..Default::default()
16        };
17        libtest_mimic::run(&args, tests()).exit();
18    }
19}
20
21fn tests() -> Vec<Trial> {
22    vec![
23        Trial::test("export_import_roundtrip_local", || {
24            block_on(export_import_roundtrip(Backend::Local));
25            Ok(())
26        }),
27        Trial::test("export_import_roundtrip_remote", || {
28            block_on(export_import_roundtrip(Backend::Remote));
29            Ok(())
30        }),
31        Trial::test("import_as_default_local", || {
32            block_on(import_as_default(Backend::Local));
33            Ok(())
34        }),
35        Trial::test("import_as_default_remote", || {
36            block_on(import_as_default(Backend::Remote));
37            Ok(())
38        }),
39        Trial::test("market_add_balance_message_on_chain", || {
40            block_on(market_add_balance_message_on_chain());
41            Ok(())
42        }),
43        Trial::test("send_to_filecoin_address_local", || {
44            block_on(send_to_filecoin_address(Backend::Local));
45            Ok(())
46        }),
47        Trial::test("send_to_filecoin_address_remote", || {
48            block_on(send_to_filecoin_address(Backend::Remote));
49            Ok(())
50        }),
51        Trial::test("send_to_eth_equivalent_local", || {
52            block_on(send_to_eth_equivalent(Backend::Local));
53            Ok(())
54        }),
55        Trial::test("send_to_eth_equivalent_remote", || {
56            block_on(send_to_eth_equivalent(Backend::Remote));
57            Ok(())
58        }),
59        Trial::test("wallet_delete_local", || {
60            block_on(wallet_delete(Backend::Local));
61            Ok(())
62        }),
63        Trial::test("wallet_delete_remote", || {
64            block_on(wallet_delete(Backend::Remote));
65            Ok(())
66        }),
67        Trial::test("delegated_send_local", || {
68            block_on(delegated_send(Backend::Local));
69            Ok(())
70        }),
71        Trial::test("delegated_send_remote", || {
72            block_on(delegated_send(Backend::Remote));
73            Ok(())
74        }),
75        Trial::test("delegated_remote_send", || {
76            block_on(delegated_remote_send());
77            Ok(())
78        }),
79    ]
80}
81
82async fn export_import_roundtrip(backend: Backend) {
83    let addr = wallet(backend, &["new"]).unwrap();
84    let exported = export_to_temp_file(&addr, backend).unwrap();
85    let path = exported
86        .path()
87        .to_str()
88        .expect("temp path is not valid UTF-8");
89
90    let deleted = wallet(backend, &["delete", &addr]).unwrap();
91    eprintln!("delete output ({}): {deleted}", backend.label());
92
93    let imported = wallet(backend, &["import", path]).unwrap();
94    assert_eq!(
95        imported,
96        addr,
97        "round-trip mismatch on {} backend: {imported} != {addr}",
98        backend.label(),
99    );
100}
101
102async fn import_as_default(backend: Backend) {
103    let addr = wallet(backend, &["new"]).unwrap();
104    let exported = export_to_temp_file(&addr, backend).unwrap();
105    let path = exported
106        .path()
107        .to_str()
108        .expect("temp path is not valid UTF-8");
109
110    let deleted = wallet(backend, &["delete", &addr]).unwrap();
111    eprintln!("delete output ({}): {deleted}", backend.label());
112
113    let imported = wallet(backend, &["import", "--as-default", path]).unwrap();
114    assert_eq!(
115        imported,
116        addr,
117        "round-trip mismatch on {} backend: {imported} != {addr}",
118        backend.label(),
119    );
120    assert_eq!(wallet(backend, &["default"]).unwrap(), imported);
121}
122
123async fn market_add_balance_message_on_chain() {
124    const ATTO_FIL: &str = "23";
125    let result = rpc_call_with_retry(
126        "Filecoin.MarketAddBalance",
127        serde_json::json!([
128            FOREST_TEST_PRELOADED_ADDRESS.as_str(),
129            FOREST_TEST_PRELOADED_ADDRESS.as_str(),
130            ATTO_FIL,
131        ]),
132    )
133    .await
134    .unwrap();
135    let msg_cid = cid_from_lotus_json_result(&result).unwrap();
136    poll_until_state_search_msg(&msg_cid).await.unwrap();
137}
138
139async fn send_to_filecoin_address(backend: Backend) {
140    let target = wallet(backend, &["new"]).unwrap();
141    let msg = send_from(&FOREST_TEST_PRELOADED_ADDRESS, &target, FIL_AMT, backend).unwrap();
142    eprintln!("send to {target} ({}) msg: {msg}", backend.label());
143    let funded = poll_until_funded(&target, backend).await.unwrap();
144    eprintln!("{target} funded balance: {funded}");
145}
146
147async fn send_to_eth_equivalent(backend: Backend) {
148    let target = wallet(backend, &["new"]).unwrap();
149    let initial_msg = send_from(&FOREST_TEST_PRELOADED_ADDRESS, &target, FIL_AMT, backend).unwrap();
150    eprintln!(
151        "initial send to {target} ({}) msg: {initial_msg}",
152        backend.label(),
153    );
154    let baseline = poll_until_funded(&target, backend).await.unwrap();
155
156    let eth = filecoin_to_eth(&target).await.unwrap();
157    let eth_msg = send_from(&FOREST_TEST_PRELOADED_ADDRESS, &eth, FIL_AMT, backend).unwrap();
158    eprintln!("send to ETH {eth} (mapped from {target}) msg: {eth_msg}");
159
160    let updated = poll_until_changed(&target, &baseline, backend)
161        .await
162        .unwrap();
163    assert!(
164        updated != baseline,
165        "{target} balance unchanged after ETH-equivalent send: {updated}",
166    );
167}
168
169async fn wallet_delete(backend: Backend) {
170    let addr = wallet(backend, &["new"]).unwrap();
171    let deleted = wallet(backend, &["delete", &addr]).unwrap();
172    eprintln!("delete output ({}): {deleted}", backend.label());
173    let listing = wallet(backend, &["list"]).unwrap();
174    assert!(
175        !listing.contains(&addr),
176        "deleted wallet {addr} still appears in `list`:\n{listing}",
177    );
178}
179
180async fn delegated_send(target_backend: Backend) {
181    let funded = funded_delegated_addr().await;
182    let target = wallet(target_backend, &["new", "delegated"]).unwrap();
183    // Baseline `FIL_ZERO` ⇒ first credit; otherwise expect a balance delta.
184    let baseline = balance(&target, target_backend).unwrap();
185    let msg = send_from(funded, &target, FIL_AMT, Backend::Local).unwrap();
186    eprintln!(
187        "delegated send to {target} ({}) msg: {msg}",
188        target_backend.label(),
189    );
190    let observed = if baseline == FIL_ZERO {
191        poll_until_funded(&target, target_backend).await.unwrap()
192    } else {
193        poll_until_changed(&target, &baseline, target_backend)
194            .await
195            .unwrap()
196    };
197    assert!(
198        observed != baseline,
199        "{target} balance unchanged after delegated send: {observed}",
200    );
201}
202
203async fn delegated_remote_send() {
204    let funded = funded_delegated_addr().await;
205    let target = wallet(Backend::Remote, &["new", "delegated"]).unwrap();
206    let baseline = balance(&target, Backend::Remote).unwrap();
207    let msg = send_from(funded, &target, FIL_AMT, Backend::Remote).unwrap();
208    eprintln!("delegated --remote-wallet send to {target} msg: {msg}");
209    let observed = if baseline == FIL_ZERO {
210        poll_until_funded(&target, Backend::Remote).await.unwrap()
211    } else {
212        poll_until_changed(&target, &baseline, Backend::Remote)
213            .await
214            .unwrap()
215    };
216    assert!(
217        observed != baseline,
218        "{target} balance unchanged after delegated --remote-wallet send: {observed}",
219    );
220}