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_utils 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 mut 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 server.unlock_lockfiles()?;
88
89 let root_id = InMemorySigner::try_from(server.validator_key.clone())?.account_id;
90
91 let info = Info {
92 name: build.name.into(),
93 root_id,
94 keystore_path: PathBuf::from(".near-credentials/sandbox/"),
95 rpc_url: url::Url::parse(&server.rpc_addr()).expect("url is hardcoded"),
96 };
97
98 Ok(Self {
99 server,
100 client,
101 info,
102 version: Some(version.to_string()),
103 })
104 }
105}
106
107impl std::fmt::Debug for Sandbox {
108 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
109 f.debug_struct("Sandbox")
110 .field("root_id", &self.info.root_id)
111 .field("rpc_url", &self.info.rpc_url)
112 .field("rpc_port", &self.server.rpc_port())
113 .field("net_port", &self.server.net_port())
114 .field("version", &self.version)
115 .finish()
116 }
117}
118
119#[async_trait]
120impl FromNetworkBuilder for Sandbox {
121 async fn from_builder<'a>(build: NetworkBuilder<'a, Self>) -> Result<Self> {
122 Self::from_builder_with_version(build, sandbox::DEFAULT_NEAR_SANDBOX_VERSION).await
123 }
124}
125
126#[async_trait]
127impl TopLevelAccountCreator for Sandbox {
128 async fn create_tla(
129 &self,
130 worker: Worker<dyn Network>,
131 id: AccountId,
132 sk: SecretKey,
133 ) -> Result<Execution<Account>> {
134 let root_signer = self.registrar_signer()?;
135 let outcome = self
136 .client()
137 .create_account(&root_signer, &id, sk.public_key(), DEFAULT_DEPOSIT)
138 .await?;
139 let signer = InMemorySigner::from_secret_key(id, sk);
140 Ok(Execution {
141 result: Account::new(signer, worker),
142 details: ExecutionFinalResult::from_view(outcome),
143 })
144 }
145
146 async fn create_tla_and_deploy(
147 &self,
148 worker: Worker<dyn Network>,
149 id: AccountId,
150 sk: SecretKey,
151 wasm: &[u8],
152 ) -> Result<Execution<Contract>> {
153 let root_signer = self.registrar_signer()?;
154 let outcome = self
155 .client()
156 .create_account_and_deploy(
157 &root_signer,
158 &id,
159 sk.public_key(),
160 DEFAULT_DEPOSIT,
161 wasm.into(),
162 )
163 .await?;
164 let signer = InMemorySigner::from_secret_key(id, sk);
165 Ok(Execution {
166 result: Contract::new(signer, worker),
167 details: ExecutionFinalResult::from_view(outcome),
168 })
169 }
170}
171
172#[async_trait]
173impl RootAccountSubaccountCreator for Sandbox {
174 fn root_account_id(&self) -> Result<AccountId> {
175 Ok(self.root_signer()?.account_id)
176 }
177
178 async fn create_root_account_subaccount(
179 &self,
180 worker: Worker<dyn Network>,
181 subaccount_prefix: AccountId,
182 sk: SecretKey,
183 ) -> Result<Execution<Account>> {
184 let id = self.compute_subaccount_id(subaccount_prefix)?;
185 let root_signer = self.root_signer()?;
186 let outcome = self
187 .client()
188 .create_account(&root_signer, &id, sk.public_key(), DEFAULT_DEPOSIT)
189 .await?;
190 let signer = InMemorySigner::from_secret_key(id, sk);
191 Ok(Execution {
192 result: Account::new(signer, worker),
193 details: ExecutionFinalResult::from_view(outcome),
194 })
195 }
196 async fn create_root_account_subaccount_and_deploy(
197 &self,
198 worker: Worker<dyn Network>,
199 subaccount_prefix: AccountId,
200 sk: SecretKey,
201 wasm: &[u8],
202 ) -> Result<Execution<Contract>> {
203 let id = self.compute_subaccount_id(subaccount_prefix)?;
204 let root_signer = self.root_signer()?;
205 let outcome = self
206 .client()
207 .create_account_and_deploy(
208 &root_signer,
209 &id,
210 sk.public_key(),
211 DEFAULT_DEPOSIT,
212 wasm.into(),
213 )
214 .await?;
215 let signer = InMemorySigner::from_secret_key(id, sk);
216 Ok(Execution {
217 result: Contract::new(signer, worker),
218 details: ExecutionFinalResult::from_view(outcome),
219 })
220 }
221}
222
223impl NetworkClient for Sandbox {
224 fn client(&self) -> &Client {
225 &self.client
226 }
227}
228
229impl NetworkInfo for Sandbox {
230 fn info(&self) -> &Info {
231 &self.info
232 }
233}
234
235impl Sandbox {
236 pub(crate) async fn patch_state(
237 &self,
238 contract_id: &AccountId,
239 key: &[u8],
240 value: &[u8],
241 ) -> Result<()> {
242 let state = StateRecord::Data {
243 account_id: contract_id.to_owned(),
244 data_key: key.to_vec().into(),
245 value: value.to_vec().into(),
246 };
247 let records = vec![state];
248
249 let _patch_resp = self
251 .client()
252 .query(&RpcSandboxPatchStateRequest { records })
253 .await
254 .map_err(|e| SandboxErrorCode::PatchStateFailure.custom(e))?;
255
256 Ok(())
257 }
258
259 pub(crate) async fn fast_forward(&self, delta_height: u64) -> Result<()> {
260 self.client()
262 .query_nolog(&RpcSandboxFastForwardRequest { delta_height })
264 .await
265 .map_err(|e| SandboxErrorCode::FastForwardFailure.custom(e))?;
266
267 Ok(())
268 }
269}