utility_workspaces/network/
sandbox.rs1use std::path::PathBuf;
2use std::str::FromStr;
3
4use async_trait::async_trait;
5use unc_jsonrpc_client::methods::sandbox_fast_forward::RpcSandboxFastForwardRequest;
6use unc_jsonrpc_client::methods::sandbox_patch_state::RpcSandboxPatchStateRequest;
7use unc_primitives::state_record::StateRecord;
8use unc_sandbox_utils as sandbox;
9
10use super::builder::{FromNetworkBuilder, NetworkBuilder};
11use super::server::ValidatorKey;
12use super::{AllowDevAccountCreation, NetworkClient, NetworkInfo, 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, SecretKey, UncToken};
19use crate::{Account, Contract, Network, Worker};
20
21const DEFAULT_DEPOSIT: UncToken = UncToken::from_unc(100);
23pub struct Sandbox {
29 pub(crate) server: SandboxServer,
30 client: Client,
31 info: Info,
32 version: Option<String>,
33}
34
35impl Sandbox {
36 pub(crate) fn root_signer(&self) -> Result<InMemorySigner> {
37 match &self.server.validator_key {
38 ValidatorKey::HomeDir(home_dir) => {
39 let path = home_dir.join("validator_key.json");
40 InMemorySigner::from_file(&path)
41 }
42 ValidatorKey::Known(account_id, secret_key) => Ok(InMemorySigner::from_secret_key(
43 account_id.clone(),
44 secret_key.clone(),
45 )),
46 }
47 }
48 pub(crate) async fn from_builder_with_version<'a>(
49 build: NetworkBuilder<'a, Self>,
50 version: &str,
51 ) -> Result<Self> {
52 let mut server = match (build.rpc_addr, build.validator_key) {
54 (Some(rpc_url), Some(validator_key)) => {
56 SandboxServer::connect(rpc_url, validator_key).await?
57 }
58
59 (None, None) => SandboxServer::run_new_with_version(version).await?,
61
62 (Some(rpc_url), None) => {
64 return Err(SandboxErrorCode::InitFailure.message(format!(
65 "Custom rpc_url={rpc_url} requires validator_key set."
66 )));
67 }
68 (None, Some(validator_key)) => {
69 return Err(SandboxErrorCode::InitFailure.message(format!(
70 "Custom validator_key={validator_key:?} requires rpc_url set."
71 )));
72 }
73 };
74
75 let client = Client::new(&server.rpc_addr(), build.api_key)?;
76 client.wait_for_rpc().await?;
77
78 server.unlock_lockfiles()?;
83
84 let info = Info {
85 name: build.name.into(),
86 root_id: AccountId::from_str("test").unwrap(),
87 keystore_path: PathBuf::from(".unc-credentials/sandbox/"),
88 rpc_url: url::Url::parse(&server.rpc_addr()).expect("url is hardcoded"),
89 };
90
91 Ok(Self {
92 server,
93 client,
94 info,
95 version: Some(version.to_string()),
96 })
97 }
98}
99
100impl std::fmt::Debug for Sandbox {
101 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
102 f.debug_struct("Sandbox")
103 .field("root_id", &self.info.root_id)
104 .field("rpc_url", &self.info.rpc_url)
105 .field("rpc_port", &self.server.rpc_port())
106 .field("net_port", &self.server.net_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_UNC_SANDBOX_VERSION).await
116 }
117}
118
119impl AllowDevAccountCreation for Sandbox {}
120
121#[async_trait]
122impl TopLevelAccountCreator for Sandbox {
123 async fn create_tla(
124 &self,
125 worker: Worker<dyn Network>,
126 id: AccountId,
127 sk: SecretKey,
128 ) -> Result<Execution<Account>> {
129 let root_signer = self.root_signer()?;
130 let outcome = self
131 .client()
132 .create_account(&root_signer, &id, sk.public_key(), DEFAULT_DEPOSIT)
133 .await?;
134
135 let signer = InMemorySigner::from_secret_key(id, sk);
136 Ok(Execution {
137 result: Account::new(signer, worker),
138 details: ExecutionFinalResult::from_view(outcome),
139 })
140 }
141
142 async fn create_account_and_deploy(
143 &self,
144 worker: Worker<dyn Network>,
145 id: AccountId,
146 sk: SecretKey,
147 wasm: &[u8],
148 ) -> Result<Execution<Contract>> {
149 let root_signer = self.root_signer()?;
150 let outcome = self
151 .client()
152 .create_account_and_deploy(
153 &root_signer,
154 &id,
155 sk.public_key(),
156 DEFAULT_DEPOSIT,
157 wasm.into(),
158 )
159 .await?;
160
161 let signer = InMemorySigner::from_secret_key(id, sk);
162 Ok(Execution {
163 result: Contract::new(signer, worker),
164 details: ExecutionFinalResult::from_view(outcome),
165 })
166 }
167}
168
169impl NetworkClient for Sandbox {
170 fn client(&self) -> &Client {
171 &self.client
172 }
173}
174
175impl NetworkInfo for Sandbox {
176 fn info(&self) -> &Info {
177 &self.info
178 }
179}
180
181impl Sandbox {
182 pub(crate) async fn patch_state(
183 &self,
184 contract_id: &AccountId,
185 key: &[u8],
186 value: &[u8],
187 ) -> Result<()> {
188 let state = StateRecord::Data {
189 account_id: contract_id.to_owned(),
190 data_key: key.to_vec().into(),
191 value: value.to_vec().into(),
192 };
193 let records = vec![state];
194
195 let _patch_resp = self
197 .client()
198 .query(&RpcSandboxPatchStateRequest { records })
199 .await
200 .map_err(|e| SandboxErrorCode::PatchStateFailure.custom(e))?;
201
202 Ok(())
203 }
204
205 pub(crate) async fn fast_forward(&self, delta_height: u64) -> Result<()> {
206 self.client()
208 .query_nolog(&RpcSandboxFastForwardRequest { delta_height })
210 .await
211 .map_err(|e| SandboxErrorCode::FastForwardFailure.custom(e))?;
212
213 Ok(())
214 }
215}