use crate::environment::blockchain_interface::BlockchainInterface;
use near_vm_logic::{
mocks::mock_external::Receipt,
types::{
AccountId, Balance, BlockHeight, Gas, PromiseIndex, PromiseResult, PublicKey, StorageUsage,
},
};
use std::cell::RefCell;
use std::mem::size_of;
use std::panic as std_panic;
thread_local! {
pub static BLOCKCHAIN_INTERFACE: RefCell<Option<Box<dyn BlockchainInterface>>>
= RefCell::new(None);
}
const BLOCKCHAIN_INTERFACE_NOT_SET_ERR: &str = "Blockchain interface not set.";
const NOT_MOCKED_BLOCKCHAIN_ERR: &str =
"Operation expects mocked blockchain, e.g. because it can be only called from unit tests.";
const REGISTER_EXPECTED_ERR: &str =
"Register was expected to have data because we just wrote it into it.";
const RETURN_CODE_ERR: &str = "Unexpected return code.";
const ATOMIC_OP_REGISTER: u64 = 0;
const EVICTED_REGISTER: u64 = std::u64::MAX - 1;
const STATE_KEY: &[u8] = b"STATE";
macro_rules! try_method_into_register {
( $method:ident ) => {{
BLOCKCHAIN_INTERFACE.with(|b| unsafe {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.$method(ATOMIC_OP_REGISTER);
});
read_register(ATOMIC_OP_REGISTER)
}};
}
macro_rules! method_into_register {
( $method:ident ) => {{
try_method_into_register!($method).expect(REGISTER_EXPECTED_ERR)
}};
}
pub fn set_blockchain_interface(blockchain_interface: Box<dyn BlockchainInterface>) {
BLOCKCHAIN_INTERFACE.with(|b| {
*b.borrow_mut() = Some(blockchain_interface);
})
}
pub fn take_blockchain_interface() -> Option<Box<dyn BlockchainInterface>> {
BLOCKCHAIN_INTERFACE.with(|b| b.replace(None))
}
fn panic_hook_impl(info: &std_panic::PanicInfo) {
panic(info.to_string().as_bytes());
}
pub fn setup_panic_hook() {
std_panic::set_hook(Box::new(panic_hook_impl));
}
pub fn read_register(register_id: u64) -> Option<Vec<u8>> {
let len = register_len(register_id)?;
let res = vec![0u8; len as usize];
BLOCKCHAIN_INTERFACE.with(|b| unsafe {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.read_register(register_id, res.as_ptr() as _)
});
Some(res)
}
pub fn register_len(register_id: u64) -> Option<u64> {
let len = BLOCKCHAIN_INTERFACE.with(|b| unsafe {
b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).register_len(register_id)
});
if len == std::u64::MAX {
None
} else {
Some(len)
}
}
pub fn current_account_id() -> AccountId {
String::from_utf8(method_into_register!(current_account_id)).unwrap()
}
pub fn signer_account_id() -> AccountId {
String::from_utf8(method_into_register!(signer_account_id)).unwrap()
}
pub fn signer_account_pk() -> PublicKey {
method_into_register!(signer_account_pk)
}
pub fn predecessor_account_id() -> String {
String::from_utf8(method_into_register!(predecessor_account_id)).unwrap()
}
pub fn input() -> Option<Vec<u8>> {
try_method_into_register!(input)
}
pub fn block_index() -> BlockHeight {
unsafe {
BLOCKCHAIN_INTERFACE
.with(|b| b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).block_index())
}
}
pub fn block_timestamp() -> u64 {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).block_timestamp()
})
}
}
pub fn epoch_height() -> u64 {
unsafe {
BLOCKCHAIN_INTERFACE
.with(|b| b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).epoch_height())
}
}
pub fn storage_usage() -> StorageUsage {
unsafe {
BLOCKCHAIN_INTERFACE
.with(|b| b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).storage_usage())
}
}
pub fn account_balance() -> Balance {
let data = [0u8; size_of::<Balance>()];
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.account_balance(data.as_ptr() as u64)
})
};
Balance::from_le_bytes(data)
}
pub fn account_locked_balance() -> Balance {
let data = [0u8; size_of::<Balance>()];
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.account_locked_balance(data.as_ptr() as u64)
})
};
Balance::from_le_bytes(data)
}
pub fn attached_deposit() -> Balance {
let data = [0u8; size_of::<Balance>()];
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.attached_deposit(data.as_ptr() as u64)
})
};
Balance::from_le_bytes(data)
}
pub fn prepaid_gas() -> Gas {
unsafe {
BLOCKCHAIN_INTERFACE
.with(|b| b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).prepaid_gas())
}
}
pub fn used_gas() -> Gas {
unsafe {
BLOCKCHAIN_INTERFACE
.with(|b| b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).used_gas())
}
}
pub fn random_seed() -> Vec<u8> {
method_into_register!(random_seed)
}
pub fn sha256(value: &[u8]) -> Vec<u8> {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).sha256(
value.len() as _,
value.as_ptr() as _,
ATOMIC_OP_REGISTER,
)
});
};
read_register(ATOMIC_OP_REGISTER).expect(REGISTER_EXPECTED_ERR)
}
pub fn keccak256(value: &[u8]) -> Vec<u8> {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).keccak256(
value.len() as _,
value.as_ptr() as _,
ATOMIC_OP_REGISTER,
)
});
};
read_register(ATOMIC_OP_REGISTER).expect(REGISTER_EXPECTED_ERR)
}
pub fn keccak512(value: &[u8]) -> Vec<u8> {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).keccak512(
value.len() as _,
value.as_ptr() as _,
ATOMIC_OP_REGISTER,
)
});
};
read_register(ATOMIC_OP_REGISTER).expect(REGISTER_EXPECTED_ERR)
}
pub fn promise_create(
account_id: AccountId,
method_name: &[u8],
arguments: &[u8],
amount: Balance,
gas: Gas,
) -> PromiseIndex {
let account_id = account_id.as_bytes();
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).promise_create(
account_id.len() as _,
account_id.as_ptr() as _,
method_name.len() as _,
method_name.as_ptr() as _,
arguments.len() as _,
arguments.as_ptr() as _,
&amount as *const Balance as _,
gas,
)
})
}
}
pub fn promise_then(
promise_idx: PromiseIndex,
account_id: AccountId,
method_name: &[u8],
arguments: &[u8],
amount: Balance,
gas: Gas,
) -> PromiseIndex {
let account_id = account_id.as_bytes();
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).promise_then(
promise_idx,
account_id.len() as _,
account_id.as_ptr() as _,
method_name.len() as _,
method_name.as_ptr() as _,
arguments.len() as _,
arguments.as_ptr() as _,
&amount as *const Balance as _,
gas,
)
})
}
}
pub fn promise_and(promise_indices: &[PromiseIndex]) -> PromiseIndex {
let mut data = vec![0u8; promise_indices.len() * size_of::<PromiseIndex>()];
for i in 0..promise_indices.len() {
data[i * size_of::<PromiseIndex>()..(i + 1) * size_of::<PromiseIndex>()]
.copy_from_slice(&promise_indices[i].to_le_bytes());
}
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.promise_and(data.as_ptr() as _, promise_indices.len() as _)
})
}
}
pub fn promise_batch_create(account_id: &AccountId) -> PromiseIndex {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.promise_batch_create(account_id.len() as _, account_id.as_ptr() as _)
})
}
}
pub fn promise_batch_then(promise_index: PromiseIndex, account_id: &AccountId) -> PromiseIndex {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).promise_batch_then(
promise_index,
account_id.len() as _,
account_id.as_ptr() as _,
)
})
}
}
pub fn promise_batch_action_create_account(promise_index: PromiseIndex) {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.promise_batch_action_create_account(promise_index)
})
}
}
pub fn promise_batch_action_deploy_contract(promise_index: u64, code: &[u8]) {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.promise_batch_action_deploy_contract(
promise_index,
code.len() as _,
code.as_ptr() as _,
)
})
}
}
pub fn promise_batch_action_function_call(
promise_index: PromiseIndex,
method_name: &[u8],
arguments: &[u8],
amount: Balance,
gas: Gas,
) {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.promise_batch_action_function_call(
promise_index,
method_name.len() as _,
method_name.as_ptr() as _,
arguments.len() as _,
arguments.as_ptr() as _,
&amount as *const Balance as _,
gas,
)
})
}
}
pub fn promise_batch_action_transfer(promise_index: PromiseIndex, amount: Balance) {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.promise_batch_action_transfer(promise_index, &amount as *const Balance as _)
})
}
}
pub fn promise_batch_action_stake(
promise_index: PromiseIndex,
amount: Balance,
public_key: &PublicKey,
) {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).promise_batch_action_stake(
promise_index,
&amount as *const Balance as _,
public_key.len() as _,
public_key.as_ptr() as _,
)
})
}
}
pub fn promise_batch_action_add_key_with_full_access(
promise_index: PromiseIndex,
public_key: &PublicKey,
nonce: u64,
) {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.promise_batch_action_add_key_with_full_access(
promise_index,
public_key.len() as _,
public_key.as_ptr() as _,
nonce,
)
})
}
}
pub fn promise_batch_action_add_key_with_function_call(
promise_index: PromiseIndex,
public_key: &PublicKey,
nonce: u64,
allowance: Balance,
receiver_id: &AccountId,
method_names: &[u8],
) {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.promise_batch_action_add_key_with_function_call(
promise_index,
public_key.len() as _,
public_key.as_ptr() as _,
nonce,
&allowance as *const Balance as _,
receiver_id.len() as _,
receiver_id.as_ptr() as _,
method_names.len() as _,
method_names.as_ptr() as _,
)
})
}
}
pub fn promise_batch_action_delete_key(promise_index: PromiseIndex, public_key: &PublicKey) {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.promise_batch_action_delete_key(
promise_index,
public_key.len() as _,
public_key.as_ptr() as _,
)
})
}
}
pub fn promise_batch_action_delete_account(
promise_index: PromiseIndex,
beneficiary_id: &AccountId,
) {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.promise_batch_action_delete_account(
promise_index,
beneficiary_id.len() as _,
beneficiary_id.as_ptr() as _,
)
})
}
}
pub fn promise_results_count() -> u64 {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).promise_results_count()
})
}
}
pub fn promise_result(result_idx: u64) -> PromiseResult {
match unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.promise_result(result_idx, ATOMIC_OP_REGISTER)
})
} {
0 => PromiseResult::NotReady,
1 => {
let data = read_register(ATOMIC_OP_REGISTER)
.expect("Promise result should've returned into register.");
PromiseResult::Successful(data)
}
2 => PromiseResult::Failed,
_ => panic!(RETURN_CODE_ERR),
}
}
pub fn promise_return(promise_idx: PromiseIndex) {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).promise_return(promise_idx)
})
}
}
pub fn value_return(value: &[u8]) {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.value_return(value.len() as _, value.as_ptr() as _)
})
}
}
pub fn panic(message: &[u8]) -> ! {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.panic_utf8(message.len() as _, message.as_ptr() as _)
})
}
unreachable!()
}
pub fn log(message: &[u8]) {
unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.log_utf8(message.len() as _, message.as_ptr() as _)
})
}
}
pub fn storage_write(key: &[u8], value: &[u8]) -> bool {
match unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).storage_write(
key.len() as _,
key.as_ptr() as _,
value.len() as _,
value.as_ptr() as _,
EVICTED_REGISTER,
)
})
} {
0 => false,
1 => true,
_ => panic!(RETURN_CODE_ERR),
}
}
pub fn storage_read(key: &[u8]) -> Option<Vec<u8>> {
match unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).storage_read(
key.len() as _,
key.as_ptr() as _,
ATOMIC_OP_REGISTER,
)
})
} {
0 => None,
1 => Some(read_register(ATOMIC_OP_REGISTER).expect(REGISTER_EXPECTED_ERR)),
_ => panic!(RETURN_CODE_ERR),
}
}
pub fn storage_remove(key: &[u8]) -> bool {
match unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow().as_ref().expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR).storage_remove(
key.len() as _,
key.as_ptr() as _,
EVICTED_REGISTER,
)
})
} {
0 => false,
1 => true,
_ => panic!(RETURN_CODE_ERR),
}
}
pub fn storage_get_evicted() -> Option<Vec<u8>> {
read_register(EVICTED_REGISTER)
}
pub fn storage_has_key(key: &[u8]) -> bool {
match unsafe {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.storage_has_key(key.len() as _, key.as_ptr() as _)
})
} {
0 => false,
1 => true,
_ => panic!(RETURN_CODE_ERR),
}
}
pub fn state_read<T: borsh::BorshDeserialize>() -> Option<T> {
storage_read(STATE_KEY)
.map(|data| T::try_from_slice(&data).expect("Cannot deserialize the contract state."))
}
pub fn state_write<T: borsh::BorshSerialize>(state: &T) {
let data = state.try_to_vec().expect("Cannot serialize the contract state.");
storage_write(STATE_KEY, &data);
}
pub fn created_receipts() -> Vec<Receipt> {
BLOCKCHAIN_INTERFACE.with(|b| {
b.borrow()
.as_ref()
.expect(BLOCKCHAIN_INTERFACE_NOT_SET_ERR)
.as_mocked_blockchain()
.expect(NOT_MOCKED_BLOCKCHAIN_ERR)
.created_receipts()
.clone()
})
}