unc_workspaces/network/
testnet.rs

1use std::path::PathBuf;
2use std::str::FromStr;
3
4use async_trait::async_trait;
5use unc_gas::UncGas;
6use url::Url;
7
8use unc_primitives::views::ExecutionStatusView;
9
10use crate::network::builder::{FromNetworkBuilder, NetworkBuilder};
11use crate::network::Info;
12use crate::network::{AllowDevAccountCreation, NetworkClient, NetworkInfo, TopLevelAccountCreator};
13use crate::result::{Execution, ExecutionDetails, ExecutionFinalResult, ExecutionOutcome, Result};
14use crate::rpc::{client::Client, tool};
15use crate::types::{AccountId, InMemorySigner, UncToken, SecretKey};
16use crate::{Account, Contract, CryptoHash, Network, Worker};
17
18/// URL to the testnet RPC node provided by unc.org.
19pub const RPC_URL: &str = "https://rpc.testnet.unc.org";
20
21/// URL to the helper contract used to create top-level-accounts (TLA) provided by unc.org.
22pub const HELPER_URL: &str = "https://helper.testnet.unc.org";
23
24/// URL to the testnet archival RPC node provided by unc.org.
25pub const ARCHIVAL_URL: &str = "https://archival-rpc.testnet.unc.org";
26
27/// Testnet related configuration for interacting with testnet. Look at
28/// [`workspaces::testnet`] and [`workspaces::testnet_archival`] for how
29/// to spin up a [`Worker`] that can be used to run tests in testnet.
30///
31/// [`workspaces::testnet`]: crate::testnet
32/// [`workspaces::testnet_archival`]: crate::testnet_archival
33/// [`Worker`]: crate::Worker
34pub struct Testnet {
35    client: Client,
36    info: Info,
37}
38
39#[async_trait]
40impl FromNetworkBuilder for Testnet {
41    async fn from_builder<'a>(build: NetworkBuilder<'a, Self>) -> Result<Self> {
42        let rpc_url = build.rpc_addr.unwrap_or_else(|| RPC_URL.into());
43        let client = Client::new(&rpc_url, build.api_key)?;
44        client.wait_for_rpc().await?;
45
46        Ok(Self {
47            client,
48            info: Info {
49                name: build.name.into(),
50                root_id: AccountId::from_str("testnet").unwrap(),
51                keystore_path: PathBuf::from(".unc-credentials/testnet/"),
52                rpc_url: Url::parse(&rpc_url).expect("url is hardcoded"),
53            },
54        })
55    }
56}
57
58impl std::fmt::Debug for Testnet {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        f.debug_struct("Testnet")
61            .field("root_id", &self.info.root_id)
62            .field("rpc_url", &self.info.rpc_url)
63            .finish()
64    }
65}
66
67impl AllowDevAccountCreation for Testnet {}
68
69#[async_trait]
70impl TopLevelAccountCreator for Testnet {
71    async fn create_tla(
72        &self,
73        worker: Worker<dyn Network>,
74        id: AccountId,
75        sk: SecretKey,
76        // TODO: return Account only, but then you don't get metadata info for it...
77    ) -> Result<Execution<Account>> {
78        let url = Url::parse(HELPER_URL).unwrap();
79        tool::url_create_account(url, id.clone(), sk.public_key()).await?;
80        let signer = InMemorySigner::from_secret_key(id, sk);
81
82        Ok(Execution {
83            result: Account::new(signer, worker),
84            details: ExecutionFinalResult {
85                // We technically have not burnt any gas ourselves since someone else paid to
86                // create the account for us in testnet when we used the Helper contract.
87                total_gas_burnt: UncGas::from_gas(0),
88
89                status: unc_primitives::views::FinalExecutionStatus::SuccessValue(Vec::new()),
90                details: ExecutionDetails {
91                    transaction: ExecutionOutcome {
92                        transaction_hash: CryptoHash::default(),
93                        block_hash: CryptoHash::default(),
94                        logs: Vec::new(),
95                        receipt_ids: Vec::new(),
96                        gas_burnt: UncGas::from_gas(0),
97                        tokens_burnt: UncToken::from_unc(0),
98                        executor_id: "testnet".parse().unwrap(),
99                        status: ExecutionStatusView::SuccessValue(Vec::new()),
100                    },
101                    receipts: Vec::new(),
102                },
103            },
104        })
105    }
106
107    async fn create_account_and_deploy(
108        &self,
109        worker: Worker<dyn Network>,
110        id: AccountId,
111        sk: SecretKey,
112        wasm: &[u8],
113    ) -> Result<Execution<Contract>> {
114        let signer = InMemorySigner::from_secret_key(id.clone(), sk.clone());
115        let account = self.create_tla(worker, id.clone(), sk).await?;
116
117        let outcome = self.client().deploy(&signer, &id, wasm.into()).await?;
118
119        Ok(Execution {
120            result: Contract::account(account.into_result()?),
121            details: ExecutionFinalResult::from_view(outcome),
122        })
123    }
124}
125
126impl NetworkClient for Testnet {
127    fn client(&self) -> &Client {
128        &self.client
129    }
130}
131
132impl NetworkInfo for Testnet {
133    fn info(&self) -> &Info {
134        &self.info
135    }
136}