use std::cell::{Ref, RefCell, RefMut};
use std::convert::TryInto;
use std::fmt::{Debug, Formatter};
use std::rc::Rc;
use near_crypto::{InMemorySigner, KeyType, PublicKey, Signer};
use near_sdk::json_types::ValidAccountId;
use near_sdk::PendingContractTx;
use crate::runtime::init_runtime;
pub use crate::to_yocto;
use crate::{
account::{AccessKey, Account},
hash::CryptoHash,
outcome_into_result,
runtime::{GenesisConfig, RuntimeStandalone},
transaction::Transaction,
types::{AccountId, Balance, Gas},
ExecutionResult, ViewResult,
};
pub const DEFAULT_GAS: u64 = 300_000_000_000_000;
pub const STORAGE_AMOUNT: u128 = 50_000_000_000_000_000_000_000_000;
type Runtime = Rc<RefCell<RuntimeStandalone>>;
pub struct UserTransaction {
transaction: Transaction,
signer: InMemorySigner,
runtime: Runtime,
}
impl UserTransaction {
pub fn submit(self) -> ExecutionResult {
let res =
(*self.runtime).borrow_mut().resolve_tx(self.transaction.sign(&self.signer)).unwrap();
(*self.runtime).borrow_mut().process_all().unwrap();
outcome_into_result(res, &self.runtime)
}
pub fn create_account(mut self) -> Self {
self.transaction = self.transaction.create_account();
self
}
pub fn deploy_contract(mut self, code: Vec<u8>) -> Self {
self.transaction = self.transaction.deploy_contract(code);
self
}
pub fn function_call(
mut self,
method_name: String,
args: Vec<u8>,
gas: Gas,
deposit: Balance,
) -> Self {
self.transaction = self.transaction.function_call(method_name, args, gas, deposit);
self
}
pub fn transfer(mut self, deposit: Balance) -> Self {
self.transaction = self.transaction.transfer(deposit);
self
}
pub fn stake(mut self, stake: Balance, public_key: PublicKey) -> Self {
self.transaction = self.transaction.stake(stake, public_key);
self
}
pub fn add_key(mut self, public_key: PublicKey, access_key: AccessKey) -> Self {
self.transaction = self.transaction.add_key(public_key, access_key);
self
}
pub fn delete_key(mut self, public_key: PublicKey) -> Self {
self.transaction = self.transaction.delete_key(public_key);
self
}
pub fn delete_account(mut self, beneficiary_id: AccountId) -> Self {
self.transaction = self.transaction.delete_account(beneficiary_id);
self
}
}
pub struct UserAccount {
runtime: Rc<RefCell<RuntimeStandalone>>,
pub account_id: AccountId,
pub signer: InMemorySigner,
}
impl Debug for UserAccount {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UserAccount").field("account_id", &self.account_id).finish()
}
}
impl UserAccount {
#[doc(hidden)]
pub fn new(
runtime: &Rc<RefCell<RuntimeStandalone>>,
account_id: AccountId,
signer: InMemorySigner,
) -> Self {
let runtime = Rc::clone(runtime);
Self { runtime, account_id, signer }
}
pub fn account_id(&self) -> AccountId {
self.account_id.clone()
}
pub fn valid_account_id(&self) -> ValidAccountId {
self.account_id().try_into().unwrap()
}
pub fn account(&self) -> Option<Account> {
(*self.runtime).borrow().view_account(&self.account_id)
}
pub fn transfer(&self, to: AccountId, deposit: Balance) -> ExecutionResult {
self.submit_transaction(self.transaction(to).transfer(deposit))
}
pub fn function_call(
&self,
pending_tx: PendingContractTx,
gas: Gas,
deposit: Balance,
) -> ExecutionResult {
self.call(
pending_tx.receiver_id.clone(),
&pending_tx.method,
&pending_tx.args,
gas,
deposit,
)
}
pub fn call(
&self,
receiver_id: AccountId,
method: &str,
args: &[u8],
gas: Gas,
deposit: Balance,
) -> ExecutionResult {
self.submit_transaction(self.transaction(receiver_id).function_call(
method.to_string(),
args.into(),
gas,
deposit,
))
}
pub fn deploy(
&self,
wasm_bytes: &[u8],
account_id: AccountId,
deposit: Balance,
) -> UserAccount {
let signer = InMemorySigner::from_seed(&account_id, KeyType::ED25519, &account_id);
self.submit_transaction(
self.transaction(account_id.clone())
.create_account()
.add_key(signer.public_key(), AccessKey::full_access())
.transfer(deposit)
.deploy_contract(wasm_bytes.to_vec()),
)
.assert_success();
UserAccount::new(&self.runtime, account_id, signer)
}
pub fn deploy_and_initialize(
&self,
wasm_bytes: &[u8],
pending_tx: PendingContractTx,
deposit: Balance,
gas: Gas,
) -> UserAccount {
self.deploy_and_init(
wasm_bytes,
pending_tx.receiver_id,
&pending_tx.method,
&pending_tx.args,
deposit,
gas,
)
}
pub fn deploy_and_init(
&self,
wasm_bytes: &[u8],
account_id: AccountId,
method: &str,
args: &[u8],
deposit: Balance,
gas: Gas,
) -> UserAccount {
let signer = InMemorySigner::from_seed(&account_id, KeyType::ED25519, &account_id);
let account_id = account_id.clone();
self.submit_transaction(
self.transaction(account_id.clone())
.create_account()
.add_key(signer.public_key(), AccessKey::full_access())
.transfer(deposit)
.deploy_contract(wasm_bytes.to_vec())
.function_call(method.to_string(), args.to_vec(), gas, 0),
)
.assert_success();
UserAccount::new(&self.runtime, account_id, signer)
}
fn transaction(&self, receiver_id: AccountId) -> Transaction {
let nonce = (*self.runtime)
.borrow()
.view_access_key(&self.account_id, &self.signer.public_key())
.unwrap()
.nonce
+ 1;
Transaction::new(
self.account_id(),
self.signer.public_key(),
receiver_id,
nonce,
CryptoHash::default(),
)
}
pub fn create_transaction(&self, receiver_id: AccountId) -> UserTransaction {
let transaction = self.transaction(receiver_id);
let runtime = Rc::clone(&self.runtime);
UserTransaction { transaction, signer: self.signer.clone(), runtime }
}
fn submit_transaction(&self, transaction: Transaction) -> ExecutionResult {
let res = (*self.runtime).borrow_mut().resolve_tx(transaction.sign(&self.signer)).unwrap();
(*self.runtime).borrow_mut().process_all().unwrap();
outcome_into_result(res, &self.runtime)
}
pub fn view_method_call(&self, pending_tx: PendingContractTx) -> ViewResult {
self.view(pending_tx.receiver_id, &pending_tx.method, &pending_tx.args)
}
pub fn view(&self, receiver_id: AccountId, method: &str, args: &[u8]) -> ViewResult {
(*self.runtime).borrow().view_method_call(&receiver_id, method, args)
}
pub fn create_user_from(
&self,
signer_user: &UserAccount,
account_id: AccountId,
amount: Balance,
) -> UserAccount {
let signer = InMemorySigner::from_seed(&account_id, KeyType::ED25519, &account_id);
signer_user
.submit_transaction(
signer_user
.transaction(account_id.clone())
.create_account()
.add_key(signer.public_key(), AccessKey::full_access())
.transfer(amount),
)
.assert_success();
UserAccount::new(&self.runtime, account_id, signer)
}
pub fn create_user(&self, account_id: AccountId, amount: Balance) -> UserAccount {
self.create_user_from(&self, account_id, amount)
}
pub fn borrow_runtime(&self) -> Ref<RuntimeStandalone> {
(*self.runtime).borrow()
}
pub fn borrow_runtime_mut(&self) -> RefMut<RuntimeStandalone> {
(*self.runtime).borrow_mut()
}
}
pub struct ContractAccount<T> {
pub user_account: UserAccount,
pub contract: T,
}
impl<T> ContractAccount<T> {
pub fn account_id(&self) -> AccountId {
self.user_account.account_id()
}
pub fn valid_account_id(&self) -> ValidAccountId {
self.user_account.valid_account_id()
}
pub fn account(&self) -> Option<Account> {
self.user_account.account()
}
}
pub fn init_simulator(genesis_config: Option<GenesisConfig>) -> UserAccount {
let (runtime, signer, root_account_id) = init_runtime(genesis_config);
UserAccount::new(&Rc::new(RefCell::new(runtime)), root_account_id, signer)
}
#[doc(inline)]
#[macro_export]
macro_rules! deploy {
($contract: ident, $account_id:expr, $wasm_bytes: expr, $user:expr) => {
deploy!($contract, $account_id, $wasm_bytes, $user, near_sdk_sim::STORAGE_AMOUNT)
};
($contract: ident, $account_id:expr, $wasm_bytes: expr, $user:expr, $deposit: expr) => {
near_sdk_sim::ContractAccount {
user_account: $user.deploy($wasm_bytes, $account_id.to_string(), $deposit),
contract: $contract { account_id: $account_id.to_string() },
}
};
($contract: ident, $account_id:expr, $wasm_bytes: expr, $user_id:expr, $deposit:expr, $gas:expr, $method: ident, $($arg:expr),* ) => {
{
let __contract = $contract { account_id: $account_id.to_string() };
near_sdk_sim::ContractAccount {
user_account: $user_id.deploy_and_initialize($wasm_bytes, __contract.$method($($arg),*), $deposit, $gas),
contract: __contract,
}
}
};
(contract: $contract: ident, contract_id: $account_id:expr, bytes: $wasm_bytes: expr, signer_account: $user:expr) => {
deploy!($contract, $account_id, $wasm_bytes, $user)
};
(contract: $contract: ident, contract_id: $account_id:expr, bytes: $wasm_bytes: expr, signer_account: $user:expr, deposit: $deposit: expr) => {
deploy!($contract, $account_id, $wasm_bytes, $user, $deposit)
};
(contract: $contract: ident, contract_id: $account_id:expr, bytes: $wasm_bytes: expr, signer_account: $user:expr, deposit: $deposit: expr, gas: $gas:expr, init_method: $method: ident($($arg:expr),*) ) => {
deploy!($contract, $account_id, $wasm_bytes, $user, $deposit, $gas, $method, $($arg),*)
};
(contract: $contract: ident, contract_id: $account_id:expr, bytes: $wasm_bytes: expr, signer_account: $user:expr, gas: $gas:expr, init_method: $method: ident($($arg:expr),*) ) => {
deploy!($contract, $account_id, $wasm_bytes, $user, near_sdk_sim::STORAGE_AMOUNT, $gas, $method, $($arg),*)
};
(contract: $contract: ident, contract_id: $account_id:expr, bytes: $wasm_bytes: expr, signer_account: $user:expr, deposit: $deposit: expr, init_method: $method: ident($($arg:expr),*) ) => {
deploy!($contract, $account_id, $wasm_bytes, $user, $deposit, near_sdk_sim::DEFAULT_GAS, $method, $($arg),*)
};
(contract: $contract: ident, contract_id: $account_id:expr, bytes: $wasm_bytes: expr, signer_account: $user:expr, init_method: $method: ident($($arg:expr),*) ) => {
deploy!($contract, $account_id, $wasm_bytes, $user, near_sdk_sim::STORAGE_AMOUNT, near_sdk_sim::DEFAULT_GAS, $method, $($arg),*)
};
}
#[macro_export]
macro_rules! call {
($signer:expr, $deposit: expr, $gas: expr, $contract: ident, $method:ident, $($arg:expr),*) => {
$signer.function_call((&$contract).contract.$method($($arg),*), $gas, $deposit)
};
($signer:expr, $contract: ident.$method:ident($($arg:expr),*), $deposit: expr, $gas: expr) => {
call!($signer, $deposit, $gas, $contract, $method, $($arg),*)
};
($signer:expr, $contract: ident.$method:ident($($arg:expr),*)) => {
call!($signer, 0, near_sdk_sim::DEFAULT_GAS, $contract, $method, $($arg),*)
};
($signer:expr, $contract: ident.$method:ident($($arg:expr),*), gas=$gas_or_deposit: expr) => {
call!($signer, 0, $gas_or_deposit, $contract, $method, $($arg),*)
};
($signer:expr, $contract: ident.$method:ident($($arg:expr),*), deposit=$gas_or_deposit: expr) => {
call!($signer, $gas_or_deposit, near_sdk_sim::DEFAULT_GAS, $contract, $method, $($arg),*)
};
}
#[macro_export]
macro_rules! view {
($contract: ident.$method:ident($($arg:expr),*)) => {
(&$contract).user_account.view_method_call((&$contract).contract.$method($($arg),*))
};
}