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 = Some(BlockId::Hash(unc_primitives::hash::CryptoHash(block_hash.0)).into());
70 self
71 }
72
73 pub fn with_data(mut self) -> Self {
79 self.import_data = true;
80 self
81 }
82
83 pub fn initial_balance(mut self, initial_balance: UncToken) -> Self {
86 self.initial_balance = Some(initial_balance);
87 self
88 }
89
90 pub fn dest_account_id(mut self, account_id: &AccountId) -> Self {
94 self.into_account_id = Some(account_id.clone());
95 self
96 }
97
98 pub async fn transact(self) -> Result<Contract> {
100 let from_account_id = self.account_id;
101 let into_account_id = self.into_account_id.as_ref().unwrap_or(from_account_id);
102
103 let sk = SecretKey::from_seed(KeyType::ED25519, DEV_ACCOUNT_SEED);
104 let pk = sk.public_key();
105 let signer = InMemorySigner::from_secret_key(into_account_id.clone(), sk);
106 let block_ref = self.block_ref.unwrap_or_else(BlockReference::latest);
107
108 let mut account_view = self
109 .from_network
110 .view_account(from_account_id)
111 .block_reference(block_ref.clone())
112 .await?;
113
114 let code_hash = account_view.code_hash;
115 if let Some(initial_balance) = self.initial_balance {
116 account_view.balance = initial_balance;
117 }
118
119 let mut patch = PatchTransaction::new(&self.into_network, into_account_id.clone())
120 .account(account_view.into())
121 .access_key(pk, AccessKey::full_access());
122
123 if code_hash != CryptoHash::default() {
124 let code = self
125 .from_network
126 .view_code(from_account_id)
127 .block_reference(block_ref.clone())
128 .await?;
129 patch = patch.code(&code);
130 }
131
132 if self.import_data {
133 let states = self
134 .from_network
135 .view_state(from_account_id)
136 .block_reference(block_ref)
137 .await?;
138
139 patch = patch.states(
140 states
141 .iter()
142 .map(|(key, value)| (key.as_slice(), value.as_slice())),
143 );
144 }
145
146 patch.transact().await?;
147 Ok(Contract::new(signer, self.into_network.coerce()))
148 }
149}
150
151enum AccountUpdate {
154 Update(AccountDetailsPatch),
155 FromCurrent(Box<dyn Fn(AccountDetails) -> AccountDetailsPatch + Send>),
156}
157
158pub struct PatchTransaction {
159 account_id: AccountId,
160 records: Vec<StateRecord>,
161 worker: Worker<Sandbox>,
162 account_updates: Vec<AccountUpdate>,
163 code_hash_update: Option<CryptoHash>,
164}
165
166impl PatchTransaction {
167 pub(crate) fn new(worker: &Worker<Sandbox>, account_id: AccountId) -> Self {
168 Self {
169 account_id,
170 records: vec![],
171 worker: worker.clone(),
172 account_updates: vec![],
173 code_hash_update: None,
174 }
175 }
176
177 pub fn account(mut self, account: AccountDetailsPatch) -> Self {
179 self.account_updates.push(AccountUpdate::Update(account));
180 self
181 }
182
183 pub fn account_from_current<F>(mut self, f: F) -> Self
187 where
188 F: Fn(AccountDetails) -> AccountDetailsPatch + Send + 'static,
189 {
190 self.account_updates
191 .push(AccountUpdate::FromCurrent(Box::new(f)));
192 self
193 }
194
195 pub fn access_key(mut self, pk: PublicKey, ak: AccessKey) -> Self {
198 self.records.push(StateRecord::AccessKey {
199 account_id: self.account_id.clone(),
200 public_key: pk.into(),
201 access_key: ak.into(),
202 });
203 self
204 }
205
206 pub fn access_keys<I>(mut self, access_keys: I) -> Self
211 where
212 I: IntoIterator<Item = (PublicKey, AccessKey)>,
213 {
214 let account_id = self.account_id;
217
218 self.records.extend(
219 access_keys
220 .into_iter()
221 .map(|(pk, ak)| StateRecord::AccessKey {
222 account_id: account_id.clone(),
223 public_key: pk.into(),
224 access_key: ak.into(),
225 }),
226 );
227
228 self.account_id = account_id;
229 self
230 }
231
232 pub fn code(mut self, wasm_bytes: &[u8]) -> Self {
236 self.code_hash_update = Some(CryptoHash::hash_bytes(wasm_bytes));
237 self.records.push(StateRecord::Contract {
238 account_id: self.account_id.clone(),
239 code: wasm_bytes.to_vec(),
240 });
241 self
242 }
243
244 pub fn state(mut self, key: &[u8], value: &[u8]) -> Self {
248 self.records.push(StateRecord::Data {
249 account_id: self.account_id.clone(),
250 data_key: key.to_vec().into(),
251 value: value.to_vec().into(),
252 });
253 self
254 }
255
256 pub fn states<'b, 'c, I>(mut self, states: I) -> Self
259 where
260 I: IntoIterator<Item = (&'b [u8], &'c [u8])>,
261 {
262 let account_id = self.account_id;
265
266 self.records
267 .extend(states.into_iter().map(|(key, value)| StateRecord::Data {
268 account_id: account_id.clone(),
269 data_key: key.to_vec().into(),
270 value: value.to_vec().into(),
271 }));
272
273 self.account_id = account_id;
274 self
275 }
276
277 pub async fn transact(mut self) -> Result<()> {
279 let account_patch = if !self.account_updates.is_empty() {
283 let mut account = AccountDetailsPatch::default();
284 for update in self.account_updates {
285 account.reduce(match update {
287 AccountUpdate::Update(account) => account,
288 AccountUpdate::FromCurrent(f) => {
289 let account = self.worker.view_account(&self.account_id).await?;
290 f(account)
291 }
292 });
293 }
294
295 if let Some(code_hash) = self.code_hash_update.take() {
297 account.code_hash = Some(code_hash);
298 }
299
300 Some(account)
301 } else if let Some(code_hash) = self.code_hash_update {
302 let mut account = self.worker.view_account(&self.account_id).await?;
305 account.code_hash = code_hash;
306 Some(account.into())
307 } else {
308 None
309 };
310
311 let records = if let Some(account) = account_patch {
314 let account: AccountDetails = account.into();
315 let mut records = vec![StateRecord::Account {
316 account_id: self.account_id.clone(),
317 account: account.into_unc_account(),
318 }];
319 records.extend(self.records);
320 records
321 } else {
322 self.records
323 };
324
325 self.worker
326 .client()
327 .query(&RpcSandboxPatchStateRequest {
328 records: records.clone(),
329 })
330 .await
331 .map_err(|err| SandboxErrorCode::PatchStateFailure.custom(err))?;
332
333 self.worker
334 .client()
335 .query(&RpcSandboxPatchStateRequest { records })
336 .await
337 .map_err(|err| SandboxErrorCode::PatchStateFailure.custom(err))?;
338 Ok(())
339 }
340}