use unc_jsonrpc_client::methods::sandbox_patch_state::RpcSandboxPatchStateRequest;
use unc_primitives::state_record::StateRecord;
use unc_primitives::types::{BlockId, BlockReference};
use unc_token::UncToken;
use crate::error::SandboxErrorCode;
use crate::network::{Sandbox, DEV_ACCOUNT_SEED};
use crate::types::account::AccountDetails;
use crate::types::{BlockHeight, KeyType, PublicKey, SecretKey};
use crate::{AccessKey, AccountDetailsPatch, Result};
use crate::{AccountId, Contract, CryptoHash, InMemorySigner, Network, Worker};
pub struct ImportContractTransaction<'a> {
account_id: &'a AccountId,
from_network: Worker<dyn Network>,
into_network: Worker<Sandbox>,
import_data: bool,
initial_balance: Option<UncToken>,
block_ref: Option<BlockReference>,
into_account_id: Option<AccountId>,
}
impl<'a> ImportContractTransaction<'a> {
pub(crate) fn new(
account_id: &'a AccountId,
from_network: Worker<dyn Network>,
into_network: Worker<Sandbox>,
) -> Self {
ImportContractTransaction {
account_id,
from_network,
into_network,
import_data: false,
initial_balance: None,
block_ref: None,
into_account_id: None,
}
}
pub fn block_height(mut self, block_height: BlockHeight) -> Self {
self.block_ref = Some(BlockId::Height(block_height).into());
self
}
pub fn block_hash(mut self, block_hash: CryptoHash) -> Self {
self.block_ref = Some(BlockId::Hash(unc_primitives::hash::CryptoHash(block_hash.0)).into());
self
}
pub fn with_data(mut self) -> Self {
self.import_data = true;
self
}
pub fn initial_balance(mut self, initial_balance: UncToken) -> Self {
self.initial_balance = Some(initial_balance);
self
}
pub fn dest_account_id(mut self, account_id: &AccountId) -> Self {
self.into_account_id = Some(account_id.clone());
self
}
pub async fn transact(self) -> Result<Contract> {
let from_account_id = self.account_id;
let into_account_id = self.into_account_id.as_ref().unwrap_or(from_account_id);
let sk = SecretKey::from_seed(KeyType::ED25519, DEV_ACCOUNT_SEED);
let pk = sk.public_key();
let signer = InMemorySigner::from_secret_key(into_account_id.clone(), sk);
let block_ref = self.block_ref.unwrap_or_else(BlockReference::latest);
let mut account_view = self
.from_network
.view_account(from_account_id)
.block_reference(block_ref.clone())
.await?;
let code_hash = account_view.code_hash;
if let Some(initial_balance) = self.initial_balance {
account_view.balance = initial_balance;
}
let mut patch = PatchTransaction::new(&self.into_network, into_account_id.clone())
.account(account_view.into())
.access_key(pk, AccessKey::full_access());
if code_hash != CryptoHash::default() {
let code = self
.from_network
.view_code(from_account_id)
.block_reference(block_ref.clone())
.await?;
patch = patch.code(&code);
}
if self.import_data {
let states = self
.from_network
.view_state(from_account_id)
.block_reference(block_ref)
.await?;
patch = patch.states(
states
.iter()
.map(|(key, value)| (key.as_slice(), value.as_slice())),
);
}
patch.transact().await?;
Ok(Contract::new(signer, self.into_network.coerce()))
}
}
enum AccountUpdate {
Update(AccountDetailsPatch),
FromCurrent(Box<dyn Fn(AccountDetails) -> AccountDetailsPatch + Send>),
}
pub struct PatchTransaction {
account_id: AccountId,
records: Vec<StateRecord>,
worker: Worker<Sandbox>,
account_updates: Vec<AccountUpdate>,
code_hash_update: Option<CryptoHash>,
}
impl PatchTransaction {
pub(crate) fn new(worker: &Worker<Sandbox>, account_id: AccountId) -> Self {
Self {
account_id,
records: vec![],
worker: worker.clone(),
account_updates: vec![],
code_hash_update: None,
}
}
pub fn account(mut self, account: AccountDetailsPatch) -> Self {
self.account_updates.push(AccountUpdate::Update(account));
self
}
pub fn account_from_current<F>(mut self, f: F) -> Self
where
F: Fn(AccountDetails) -> AccountDetailsPatch + Send + 'static,
{
self.account_updates
.push(AccountUpdate::FromCurrent(Box::new(f)));
self
}
pub fn access_key(mut self, pk: PublicKey, ak: AccessKey) -> Self {
self.records.push(StateRecord::AccessKey {
account_id: self.account_id.clone(),
public_key: pk.into(),
access_key: ak.into(),
});
self
}
pub fn access_keys<I>(mut self, access_keys: I) -> Self
where
I: IntoIterator<Item = (PublicKey, AccessKey)>,
{
let account_id = self.account_id;
self.records.extend(
access_keys
.into_iter()
.map(|(pk, ak)| StateRecord::AccessKey {
account_id: account_id.clone(),
public_key: pk.into(),
access_key: ak.into(),
}),
);
self.account_id = account_id;
self
}
pub fn code(mut self, wasm_bytes: &[u8]) -> Self {
self.code_hash_update = Some(CryptoHash::hash_bytes(wasm_bytes));
self.records.push(StateRecord::Contract {
account_id: self.account_id.clone(),
code: wasm_bytes.to_vec(),
});
self
}
pub fn state(mut self, key: &[u8], value: &[u8]) -> Self {
self.records.push(StateRecord::Data {
account_id: self.account_id.clone(),
data_key: key.to_vec().into(),
value: value.to_vec().into(),
});
self
}
pub fn states<'b, 'c, I>(mut self, states: I) -> Self
where
I: IntoIterator<Item = (&'b [u8], &'c [u8])>,
{
let account_id = self.account_id;
self.records
.extend(states.into_iter().map(|(key, value)| StateRecord::Data {
account_id: account_id.clone(),
data_key: key.to_vec().into(),
value: value.to_vec().into(),
}));
self.account_id = account_id;
self
}
pub async fn transact(mut self) -> Result<()> {
let account_patch = if !self.account_updates.is_empty() {
let mut account = AccountDetailsPatch::default();
for update in self.account_updates {
account.reduce(match update {
AccountUpdate::Update(account) => account,
AccountUpdate::FromCurrent(f) => {
let account = self.worker.view_account(&self.account_id).await?;
f(account)
}
});
}
if let Some(code_hash) = self.code_hash_update.take() {
account.code_hash = Some(code_hash);
}
Some(account)
} else if let Some(code_hash) = self.code_hash_update {
let mut account = self.worker.view_account(&self.account_id).await?;
account.code_hash = code_hash;
Some(account.into())
} else {
None
};
let records = if let Some(account) = account_patch {
let account: AccountDetails = account.into();
let mut records = vec![StateRecord::Account {
account_id: self.account_id.clone(),
account: account.into_unc_account(),
}];
records.extend(self.records);
records
} else {
self.records
};
self.worker
.client()
.query(&RpcSandboxPatchStateRequest {
records: records.clone(),
})
.await
.map_err(|err| SandboxErrorCode::PatchStateFailure.custom(err))?;
self.worker
.client()
.query(&RpcSandboxPatchStateRequest { records })
.await
.map_err(|err| SandboxErrorCode::PatchStateFailure.custom(err))?;
Ok(())
}
}