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