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
21const DEFAULT_DEPOSIT: NearToken = NearToken::from_near(100);
23pub 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 let server = match (build.rpc_addr, build.validator_key) {
61 (Some(rpc_url), Some(validator_key)) => SandboxServer::new(rpc_url, validator_key)?,
63
64 (None, None) => SandboxServer::run_new_with_version(version).await?,
66
67 (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 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 self.client()
255 .query_nolog(&RpcSandboxFastForwardRequest { delta_height })
257 .await
258 .map_err(|e| SandboxErrorCode::FastForwardFailure.custom(e))?;
259
260 Ok(())
261 }
262}