Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parameterize Engine Access to Storage #314

Merged
merged 1 commit into from
Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions engine-precompiles/src/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,12 @@ impl ExitToNear {

#[cfg(feature = "contract")]
fn get_nep141_from_erc20(erc20_token: &[u8]) -> AccountId {
use sdk::io::{StorageIntermediate, IO};
AccountId::try_from(
&sdk::read_storage(bytes_to_key(KeyPrefix::Erc20Nep141Map, erc20_token).as_slice())
.expect(ERR_TARGET_TOKEN_NOT_FOUND)[..],
sdk::near_runtime::Runtime
.read_storage(bytes_to_key(KeyPrefix::Erc20Nep141Map, erc20_token).as_slice())
.map(|s| s.to_vec())
.expect(ERR_TARGET_TOKEN_NOT_FOUND),
)
.unwrap()
}
Expand Down
47 changes: 47 additions & 0 deletions engine-sdk/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#[derive(Debug)]
pub struct BorshDeserializeError;

impl AsRef<[u8]> for BorshDeserializeError {
fn as_ref(&self) -> &[u8] {
b"ERR_ARG_PARSE"
}
}

#[derive(Debug)]
pub struct IncorrectInputLength;

impl AsRef<[u8]> for IncorrectInputLength {
fn as_ref(&self) -> &[u8] {
b"ERR_INCORRECT_INPUT_LENGTH"
}
}

#[derive(Debug)]
pub enum ReadU64Error {
InvalidU64,
MissingValue,
}

impl AsRef<[u8]> for ReadU64Error {
fn as_ref(&self) -> &[u8] {
match self {
Self::InvalidU64 => b"ERR_NOT_U64",
Self::MissingValue => b"ERR_U64_NOT_FOUND",
}
}
}

#[derive(Debug)]
pub enum ReadU256Error {
InvalidU256,
MissingValue,
}

impl AsRef<[u8]> for ReadU256Error {
fn as_ref(&self) -> &[u8] {
match self {
Self::InvalidU256 => b"ERR_NOT_U256",
Self::MissingValue => b"ERR_U256_NOT_FOUND",
}
}
}
138 changes: 138 additions & 0 deletions engine-sdk/src/io.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use crate::error;
use crate::prelude::{vec, Vec};
use aurora_engine_types::U256;
use borsh::{BorshDeserialize, BorshSerialize};

/// The purpose of this trait is to represent a reference to a value that
/// could be obtained by IO, but without eagerly loading it into memory.
/// For example, the NEAR runtime registers API allows querying the length
/// of some bytes read from input or storage without loading them into the
/// wasm memory.
pub trait StorageIntermediate: Sized {
fn len(&self) -> usize;
fn is_empty(&self) -> bool;
fn copy_to_slice(&self, buffer: &mut [u8]);

fn to_vec(&self) -> Vec<u8> {
let size = self.len();
let mut buf = vec![0u8; size];
self.copy_to_slice(&mut buf);
buf
}

fn to_value<T: BorshDeserialize>(&self) -> Result<T, error::BorshDeserializeError> {
let bytes = self.to_vec();
T::try_from_slice(&bytes).map_err(|_| error::BorshDeserializeError)
}
}

/// Trait for reading/writing values from storage and a generalized `stdin`/`stdout`.
pub trait IO {
/// A type giving a reference to a value obtained by IO without loading it
/// into memory. For example, in the case of a wasm contract on NEAR this
/// will correspond to a register index.
type StorageValue: StorageIntermediate;

/// Read bytes that were passed as input to the process. This can be thought of as a
/// generalization of `stdin` or command-line arguments. In the case of wasm contracts
/// on NEAR these would be the arguments to the method.
fn read_input(&self) -> Self::StorageValue;

/// Return a value to an external process. In the case of wasm contracts on NEAR
/// this corresponds to the return value from the contract method.
fn return_output(&mut self, value: &[u8]);

/// Read the value in storage at the given key, if any.
fn read_storage(&self, key: &[u8]) -> Option<Self::StorageValue>;

/// Check if there is a value in storage at the given key, but do not read the value.
/// Equivalent to `self.read_storage(key).is_some()` but more efficient.
fn storage_has_key(&self, key: &[u8]) -> bool;

/// Write the given value to storage under the given key. Returns a reference to the old
/// value stored at that key (if any).
fn write_storage(&mut self, key: &[u8], value: &[u8]) -> Option<Self::StorageValue>;

/// Write a `StorageIntermediate` to storage directly under the given key
/// (without ever needing to load the value into memory).Returns a reference
/// to the old value stored at that key (if any).
fn write_storage_direct(
&mut self,
key: &[u8],
value: Self::StorageValue,
) -> Option<Self::StorageValue>;

/// Remove entry from storage and capture the value present at the given key (if any)
fn remove_storage(&mut self, key: &[u8]) -> Option<Self::StorageValue>;

/// Read the length of the bytes stored at the given key.
fn read_storage_len(&self, key: &[u8]) -> Option<usize> {
self.read_storage(key).map(|s| s.len())
}

/// Convenience function to read the input and deserialize the bytes using borsh.
fn read_input_borsh<U: BorshDeserialize>(&self) -> Result<U, error::BorshDeserializeError> {
self.read_input().to_value()
}

/// Convenience function to read the input into a 20-byte array.
fn read_input_arr20(&self) -> Result<[u8; 20], error::IncorrectInputLength> {
let value = self.read_input();

if value.len() != 20 {
return Err(error::IncorrectInputLength);
}

let mut buf = [0u8; 20];
value.copy_to_slice(&mut buf);
Ok(buf)
}

/// Convenience function to store the input directly in storage under the
/// given key (without ever loading it into memory).
fn read_input_and_store(&mut self, key: &[u8]) {
let value = self.read_input();
self.write_storage_direct(key, value);
}

/// Convenience function to read a 64-bit unsigned integer from storage
/// (assumes little-endian encoding).
fn read_u64(&self, key: &[u8]) -> Result<u64, error::ReadU64Error> {
let value = self
.read_storage(key)
.ok_or(error::ReadU64Error::MissingValue)?;

if value.len() != 8 {
return Err(error::ReadU64Error::InvalidU64);
}

let mut result = [0u8; 8];
value.copy_to_slice(&mut result);
Ok(u64::from_le_bytes(result))
}

/// Convenience function to read a 256-bit unsigned integer from storage
/// (assumes big-endian encoding).
fn read_u256(&self, key: &[u8]) -> Result<U256, error::ReadU256Error> {
let value = self
.read_storage(key)
.ok_or(error::ReadU256Error::MissingValue)?;

if value.len() != 32 {
return Err(error::ReadU256Error::InvalidU256);
}

let mut result = [0u8; 32];
value.copy_to_slice(&mut result);
Ok(U256::from_big_endian(&result))
}

fn write_borsh<T: BorshSerialize>(
&mut self,
key: &[u8],
value: &T,
) -> Option<Self::StorageValue> {
let bytes = value.try_to_vec().ok()?;
self.write_storage(key, &bytes)
}
}
Loading