1use unc_jsonrpc_client::methods::sandbox_patch_state::RpcSandboxPatchStateRequest;
2use unc_primitives::state_record::StateRecord;
3use unc_primitives::types::{BlockId, BlockReference};
4use unc_token::UncToken;
5
6use crate::error::SandboxErrorCode;
7use crate::network::{Sandbox, DEV_ACCOUNT_SEED};
8use crate::types::account::AccountDetails;
9use crate::types::{BlockHeight, KeyType, PublicKey, SecretKey};
10use crate::{AccessKey, AccountDetailsPatch, Result};
11use crate::{AccountId, Contract, CryptoHash, InMemorySigner, Network, Worker};
12
13pub struct ImportContractTransaction<'a> {
21 account_id: &'a AccountId,
22 from_network: Worker<dyn Network>,
23 into_network: Worker<Sandbox>,
24
25 import_data: bool,
27
28 initial_balance: Option<UncToken>,
31
32 block_ref: Option<BlockReference>,
33
34 into_account_id: Option<AccountId>,
36}
37
38impl<'a> ImportContractTransaction<'a> {
39 pub(crate) fn new(
40 account_id: &'a AccountId,
41 from_network: Worker<dyn Network>,
42 into_network: Worker<Sandbox>,
43 ) -> Self {
44 ImportContractTransaction {
45 account_id,
46 from_network,
47 into_network,
48 import_data: false,
49 initial_balance: None,
50 block_ref: None,
51 into_account_id: None,
52 }
53 }
54
55 pub fn block_height(mut self, block_height: BlockHeight) -> Self {
60 self.block_ref = Some(BlockId::Height(block_height).into());
61 self
62 }
63
64 pub fn block_hash(mut self, block_hash: CryptoHash) -> Self {
69 self.block_ref =
70 Some(BlockId::Hash(unc_primitives::hash::CryptoHash(block_hash.0)).into());
71 self
72 }
73
74 pub fn with_data(mut self) -> Self {
80 self.import_data = true;
81 self
82 }
83
84 pub fn initial_balance(mut self, initial_balance: UncToken) -> Self {
87 self.initial_balance = Some(initial_balance);
88 self
89 }
90
91 pub fn dest_account_id(mut self, account_id: &AccountId) -> Self {
95 self.into_account_id = Some(account_id.clone());
96 self
97 }
98
99 pub async fn transact(self) -> Result<Contract> {
101 let from_account_id = self.account_id;
102 let into_account_id = self.into_account_id.as_ref().unwrap_or(from_account_id);
103
104 let sk = SecretKey::from_seed(KeyType::ED25519, DEV_ACCOUNT_SEED);
105 let pk = sk.public_key();
106 let signer = InMemorySigner::from_secret_key(into_account_id.clone(), sk);
107 let block_ref = self.block_ref.unwrap_or_else(BlockReference::latest);
108
109 let mut account_view = self
110 .from_network
111 .view_account(from_account_id)
112 .block_reference(block_ref.clone())
113 .await?;
114
115 let code_hash = account_view.code_hash;
116 if let Some(initial_balance) = self.initial_balance {
117 account_view.balance = initial_balance;
118 }
119
120 let mut patch = PatchTransaction::new(&self.into_network, into_account_id.clone())
121 .account(account_view.into())
122 .access_key(pk, AccessKey::full_access());
123
124 if code_hash != CryptoHash::default() {
125 let code = self
126 .from_network
127 .view_code(from_account_id)
128 .block_reference(block_ref.clone())
129 .await?;
130 patch = patch.code(&code);
131 }
132
133 if self.import_data {
134 let states = self
135 .from_network
136 .view_state(from_account_id)
137 .block_reference(block_ref)
138 .await?;
139
140 patch = patch.states(
141 states
142 .iter()
143 .map(|(key, value)| (key.as_slice(), value.as_slice())),
144 );
145 }
146
147 patch.transact().await?;
148 Ok(Contract::new(signer, self.into_network.coerce()))
149 }
150}
151
152enum AccountUpdate {
155 Update(AccountDetailsPatch),
156 FromCurrent(Box<dyn Fn(AccountDetails) -> AccountDetailsPatch + Send>),
157}
158
159pub struct PatchTransaction {
160 account_id: AccountId,
161 records: Vec<StateRecord>,
162 worker: Worker<Sandbox>,
163 account_updates: Vec<AccountUpdate>,
164 code_hash_update: Option<CryptoHash>,
165}
166
167impl PatchTransaction {
168 pub(crate) fn new(worker: &Worker<Sandbox>, account_id: AccountId) -> Self {
169 Self {
170 account_id,
171 records: vec![],
172 worker: worker.clone(),
173 account_updates: vec![],
174 code_hash_update: None,
175 }
176 }
177
178 pub fn account(mut self, account: AccountDetailsPatch) -> Self {
180 self.account_updates.push(AccountUpdate::Update(account));
181 self
182 }
183
184 pub fn account_from_current<F>(mut self, f: F) -> Self
188 where
189 F: Fn(AccountDetails) -> AccountDetailsPatch + Send + 'static,
190 {
191 self.account_updates
192 .push(AccountUpdate::FromCurrent(Box::new(f)));
193 self
194 }
195
196 pub fn access_key(mut self, pk: PublicKey, ak: AccessKey) -> Self {
199 self.records.push(StateRecord::AccessKey {
200 account_id: self.account_id.clone(),
201 public_key: pk.into(),
202 access_key: ak.into(),
203 });
204 self
205 }
206
207 pub fn access_keys<I>(mut self, access_keys: I) -> Self
212 where
213 I: IntoIterator<Item = (PublicKey, AccessKey)>,
214 {
215 let account_id = self.account_id;
218
219 self.records.extend(
220 access_keys
221 .into_iter()
222 .map(|(pk, ak)| StateRecord::AccessKey {
223 account_id: account_id.clone(),
224 public_key: pk.into(),
225 access_key: ak.into(),
226 }),
227 );
228
229 self.account_id = account_id;
230 self
231 }
232
233 pub fn code(mut self, wasm_bytes: &[u8]) -> Self {
237 self.code_hash_update = Some(CryptoHash::hash_bytes(wasm_bytes));
238 self.records.push(StateRecord::Contract {
239 account_id: self.account_id.clone(),
240 code: wasm_bytes.to_vec(),
241 });
242 self
243 }
244
245 pub fn state(mut self, key: &[u8], value: &[u8]) -> Self {
249 self.records.push(StateRecord::Data {
250 account_id: self.account_id.clone(),
251 data_key: key.to_vec().into(),
252 value: value.to_vec().into(),
253 });
254 self
255 }
256
257 pub fn states<'b, 'c, I>(mut self, states: I) -> Self
260 where
261 I: IntoIterator<Item = (&'b [u8], &'c [u8])>,
262 {
263 let account_id = self.account_id;
266
267 self.records
268 .extend(states.into_iter().map(|(key, value)| StateRecord::Data {
269 account_id: account_id.clone(),
270 data_key: key.to_vec().into(),
271 value: value.to_vec().into(),
272 }));
273
274 self.account_id = account_id;
275 self
276 }
277
278 pub async fn transact(mut self) -> Result<()> {
280 let account_patch = if !self.account_updates.is_empty() {
284 let mut account = AccountDetailsPatch::default();
285 for update in self.account_updates {
286 account.reduce(match update {
288 AccountUpdate::Update(account) => account,
289 AccountUpdate::FromCurrent(f) => {
290 let account = self.worker.view_account(&self.account_id).await?;
291 f(account)
292 }
293 });
294 }
295
296 if let Some(code_hash) = self.code_hash_update.take() {
298 account.code_hash = Some(code_hash);
299 }
300
301 Some(account)
302 } else if let Some(code_hash) = self.code_hash_update {
303 let mut account = self.worker.view_account(&self.account_id).await?;
306 account.code_hash = code_hash;
307 Some(account.into())
308 } else {
309 None
310 };
311
312 let records = if let Some(account) = account_patch {
315 let account: AccountDetails = account.into();
316 let mut records = vec![StateRecord::Account {
317 account_id: self.account_id.clone(),
318 account: account.into_unc_account(),
319 }];
320 records.extend(self.records);
321 records
322 } else {
323 self.records
324 };
325
326 self.worker
327 .client()
328 .query(&RpcSandboxPatchStateRequest {
329 records: records.clone(),
330 })
331 .await
332 .map_err(|err| SandboxErrorCode::PatchStateFailure.custom(err))?;
333
334 self.worker
335 .client()
336 .query(&RpcSandboxPatchStateRequest { records })
337 .await
338 .map_err(|err| SandboxErrorCode::PatchStateFailure.custom(err))?;
339 Ok(())
340 }
341}