near_workspaces/network/
sandbox.rs

1use std::convert::TryFrom;
2use std::path::PathBuf;
3
4use async_trait::async_trait;
5use near_jsonrpc_client::methods::sandbox_fast_forward::RpcSandboxFastForwardRequest;
6use near_jsonrpc_client::methods::sandbox_patch_state::RpcSandboxPatchStateRequest;
7use near_primitives::state_record::StateRecord;
8use near_sandbox as sandbox;
9
10use super::builder::{FromNetworkBuilder, NetworkBuilder};
11use super::server::ValidatorKey;
12use super::{NetworkClient, NetworkInfo, RootAccountSubaccountCreator, TopLevelAccountCreator};
13use crate::error::SandboxErrorCode;
14use crate::network::server::SandboxServer;
15use crate::network::Info;
16use crate::result::{Execution, ExecutionFinalResult, Result};
17use crate::rpc::client::Client;
18use crate::types::{AccountId, InMemorySigner, NearToken, SecretKey};
19use crate::{Account, Contract, Network, Worker};
20
21// Constant taken from nearcore crate to avoid dependency
22const DEFAULT_DEPOSIT: NearToken = NearToken::from_near(100);
23/// Local sandboxed environment/network
24///
25/// Can be used to test without interacting with
26/// networks that are online such as mainnet and testnet. Look at [`workspaces::sandbox`]
27/// for how to spin up a sandboxed network and interact with it.
28///
29/// [`workspaces::sandbox`]: crate::sandbox
30pub struct Sandbox {
31    pub(crate) server: SandboxServer,
32    client: Client,
33    info: Info,
34    version: Option<String>,
35}
36
37impl Sandbox {
38    pub(crate) fn root_signer(&self) -> Result<InMemorySigner> {
39        InMemorySigner::try_from(self.server.validator_key.clone())
40    }
41
42    pub(crate) fn registrar_signer(&self) -> Result<InMemorySigner> {
43        match &self.server.validator_key {
44            ValidatorKey::HomeDir(home_dir) => {
45                let path = home_dir.join("registrar.json");
46                InMemorySigner::from_file(&path)
47            }
48            ValidatorKey::Known(account_id, secret_key) => Ok(InMemorySigner::from_secret_key(
49                account_id.clone(),
50                secret_key.clone(),
51            )),
52        }
53    }
54
55    pub(crate) async fn from_builder_with_version(
56        build: NetworkBuilder<'_, Self>,
57        version: &str,
58    ) -> Result<Self> {
59        // Check the conditions of the provided rpc_url and validator_key
60        let server = match (build.rpc_addr, build.validator_key) {
61            // Connect to a provided sandbox:
62            (Some(rpc_url), Some(validator_key)) => SandboxServer::new(rpc_url, validator_key)?,
63
64            // Spawn a new sandbox since rpc_url and home_dir weren't specified:
65            (None, None) => SandboxServer::run_new_with_version(version).await?,
66
67            // Missing inputted parameters for sandbox:
68            (Some(rpc_url), None) => {
69                return Err(SandboxErrorCode::InitFailure.message(format!(
70                    "Custom rpc_url={rpc_url} requires validator_key set."
71                )));
72            }
73            (None, Some(validator_key)) => {
74                return Err(SandboxErrorCode::InitFailure.message(format!(
75                    "Custom validator_key={validator_key:?} requires rpc_url set."
76                )));
77            }
78        };
79
80        let client = Client::new(&server.rpc_addr(), build.api_key)?;
81        client.wait_for_rpc().await?;
82
83        let root_id = InMemorySigner::try_from(server.validator_key.clone())?.account_id;
84
85        let info = Info {
86            name: build.name.into(),
87            root_id,
88            keystore_path: PathBuf::from(".near-credentials/sandbox/"),
89            rpc_url: url::Url::parse(&server.rpc_addr()).expect("url is hardcoded"),
90        };
91
92        Ok(Self {
93            server,
94            client,
95            info,
96            version: Some(version.to_string()),
97        })
98    }
99}
100
101impl std::fmt::Debug for Sandbox {
102    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
103        f.debug_struct("Sandbox")
104            .field("root_id", &self.info.root_id)
105            .field("rpc_url", &self.info.rpc_url)
106            .field("rpc_port", &self.server.rpc_port())
107            .field("version", &self.version)
108            .finish()
109    }
110}
111
112#[async_trait]
113impl FromNetworkBuilder for Sandbox {
114    async fn from_builder<'a>(build: NetworkBuilder<'a, Self>) -> Result<Self> {
115        Self::from_builder_with_version(build, sandbox::DEFAULT_NEAR_SANDBOX_VERSION).await
116    }
117}
118
119#[async_trait]
120impl TopLevelAccountCreator for Sandbox {
121    async fn create_tla(
122        &self,
123        worker: Worker<dyn Network>,
124        id: AccountId,
125        sk: SecretKey,
126    ) -> Result<Execution<Account>> {
127        let root_signer = self.registrar_signer()?;
128        let outcome = self
129            .client()
130            .create_account(&root_signer, &id, sk.public_key(), DEFAULT_DEPOSIT)
131            .await?;
132        let signer = InMemorySigner::from_secret_key(id, sk);
133        Ok(Execution {
134            result: Account::new(signer, worker),
135            details: ExecutionFinalResult::from_view(outcome),
136        })
137    }
138
139    async fn create_tla_and_deploy(
140        &self,
141        worker: Worker<dyn Network>,
142        id: AccountId,
143        sk: SecretKey,
144        wasm: &[u8],
145    ) -> Result<Execution<Contract>> {
146        let root_signer = self.registrar_signer()?;
147        let outcome = self
148            .client()
149            .create_account_and_deploy(
150                &root_signer,
151                &id,
152                sk.public_key(),
153                DEFAULT_DEPOSIT,
154                wasm.into(),
155            )
156            .await?;
157        let signer = InMemorySigner::from_secret_key(id, sk);
158        Ok(Execution {
159            result: Contract::new(signer, worker),
160            details: ExecutionFinalResult::from_view(outcome),
161        })
162    }
163}
164
165#[async_trait]
166impl RootAccountSubaccountCreator for Sandbox {
167    fn root_account_id(&self) -> Result<AccountId> {
168        Ok(self.root_signer()?.account_id)
169    }
170
171    async fn create_root_account_subaccount(
172        &self,
173        worker: Worker<dyn Network>,
174        subaccount_prefix: AccountId,
175        sk: SecretKey,
176    ) -> Result<Execution<Account>> {
177        let id = self.compute_subaccount_id(subaccount_prefix)?;
178        let root_signer = self.root_signer()?;
179        let outcome = self
180            .client()
181            .create_account(&root_signer, &id, sk.public_key(), DEFAULT_DEPOSIT)
182            .await?;
183        let signer = InMemorySigner::from_secret_key(id, sk);
184        Ok(Execution {
185            result: Account::new(signer, worker),
186            details: ExecutionFinalResult::from_view(outcome),
187        })
188    }
189    async fn create_root_account_subaccount_and_deploy(
190        &self,
191        worker: Worker<dyn Network>,
192        subaccount_prefix: AccountId,
193        sk: SecretKey,
194        wasm: &[u8],
195    ) -> Result<Execution<Contract>> {
196        let id = self.compute_subaccount_id(subaccount_prefix)?;
197        let root_signer = self.root_signer()?;
198        let outcome = self
199            .client()
200            .create_account_and_deploy(
201                &root_signer,
202                &id,
203                sk.public_key(),
204                DEFAULT_DEPOSIT,
205                wasm.into(),
206            )
207            .await?;
208        let signer = InMemorySigner::from_secret_key(id, sk);
209        Ok(Execution {
210            result: Contract::new(signer, worker),
211            details: ExecutionFinalResult::from_view(outcome),
212        })
213    }
214}
215
216impl NetworkClient for Sandbox {
217    fn client(&self) -> &Client {
218        &self.client
219    }
220}
221
222impl NetworkInfo for Sandbox {
223    fn info(&self) -> &Info {
224        &self.info
225    }
226}
227
228impl Sandbox {
229    pub(crate) async fn patch_state(
230        &self,
231        contract_id: &AccountId,
232        key: &[u8],
233        value: &[u8],
234    ) -> Result<()> {
235        let state = StateRecord::Data {
236            account_id: contract_id.to_owned(),
237            data_key: key.to_vec().into(),
238            value: value.to_vec().into(),
239        };
240        let records = vec![state];
241
242        // NOTE: RpcSandboxPatchStateResponse is an empty struct with no fields, so don't do anything with it:
243        let _patch_resp = self
244            .client()
245            .query(&RpcSandboxPatchStateRequest { records })
246            .await
247            .map_err(|e| SandboxErrorCode::PatchStateFailure.custom(e))?;
248
249        Ok(())
250    }
251
252    pub(crate) async fn fast_forward(&self, delta_height: u64) -> Result<()> {
253        // NOTE: RpcSandboxFastForwardResponse is an empty struct with no fields, so don't do anything with it:
254        self.client()
255            // TODO: replace this with the `query` variant when RpcSandboxFastForwardRequest impls Debug
256            .query_nolog(&RpcSandboxFastForwardRequest { delta_height })
257            .await
258            .map_err(|e| SandboxErrorCode::FastForwardFailure.custom(e))?;
259
260        Ok(())
261    }
262}