use std::fmt::Debug;
use std::ops::AddAssign;
use std::string::FromUtf8Error;
use thiserror::Error;
use cosmwasm_std::{Binary, ContractResult, SystemResult};
#[cfg(feature = "iterator")]
use cosmwasm_std::{Order, Record};
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct GasInfo {
pub cost: u64,
pub externally_used: u64,
}
impl GasInfo {
pub fn new(cost: u64, externally_used: u64) -> Self {
GasInfo {
cost,
externally_used,
}
}
pub fn with_cost(amount: u64) -> Self {
GasInfo {
cost: amount,
externally_used: 0,
}
}
pub fn with_externally_used(amount: u64) -> Self {
GasInfo {
cost: 0,
externally_used: amount,
}
}
pub fn free() -> Self {
GasInfo {
cost: 0,
externally_used: 0,
}
}
}
impl AddAssign for GasInfo {
fn add_assign(&mut self, other: Self) {
*self = GasInfo {
cost: self.cost + other.cost,
externally_used: self.externally_used + other.externally_used,
};
}
}
pub struct Backend<A: BackendApi, S: Storage, Q: Querier> {
pub api: A,
pub storage: S,
pub querier: Q,
}
pub trait Storage {
fn get(&self, key: &[u8]) -> BackendResult<Option<Vec<u8>>>;
#[cfg(feature = "iterator")]
fn scan(
&mut self,
start: Option<&[u8]>,
end: Option<&[u8]>,
order: Order,
) -> BackendResult<u32>;
#[cfg(feature = "iterator")]
fn next(&mut self, iterator_id: u32) -> BackendResult<Option<Record>>;
#[cfg(feature = "iterator")]
fn next_value(&mut self, iterator_id: u32) -> BackendResult<Option<Vec<u8>>> {
let (result, gas_info) = self.next(iterator_id);
let result = result.map(|record| record.map(|(_, v)| v));
(result, gas_info)
}
#[cfg(feature = "iterator")]
fn next_key(&mut self, iterator_id: u32) -> BackendResult<Option<Vec<u8>>> {
let (result, gas_info) = self.next(iterator_id);
let result = result.map(|record| record.map(|(k, _)| k));
(result, gas_info)
}
fn set(&mut self, key: &[u8], value: &[u8]) -> BackendResult<()>;
fn remove(&mut self, key: &[u8]) -> BackendResult<()>;
}
pub trait BackendApi: Clone + Send {
fn addr_validate(&self, input: &str) -> BackendResult<()>;
fn addr_canonicalize(&self, human: &str) -> BackendResult<Vec<u8>>;
fn addr_humanize(&self, canonical: &[u8]) -> BackendResult<String>;
}
pub trait Querier {
fn query_raw(
&self,
request: &[u8],
gas_limit: u64,
) -> BackendResult<SystemResult<ContractResult<Binary>>>;
}
pub type BackendResult<T> = (core::result::Result<T, BackendError>, GasInfo);
macro_rules! unwrap_or_return_with_gas {
($result: expr $(,)?, $gas_total: expr $(,)?) => {{
let result: core::result::Result<_, _> = $result; let gas: GasInfo = $gas_total; match result {
Ok(v) => v,
Err(e) => return (Err(e), gas),
}
}};
}
pub(crate) use unwrap_or_return_with_gas;
#[derive(Error, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BackendError {
#[error("Panic in FFI call")]
ForeignPanic {},
#[error("Bad argument")]
BadArgument {},
#[error("VM received invalid UTF-8 data from backend")]
InvalidUtf8 {},
#[error("Iterator with ID {id} does not exist")]
IteratorDoesNotExist { id: u32 },
#[error("Ran out of gas during call into backend")]
OutOfGas {},
#[error("Unknown error during call into backend: {msg}")]
Unknown { msg: String },
#[error("User error during call into backend: {msg}")]
UserErr { msg: String },
}
impl BackendError {
pub fn foreign_panic() -> Self {
BackendError::ForeignPanic {}
}
pub fn bad_argument() -> Self {
BackendError::BadArgument {}
}
pub fn iterator_does_not_exist(iterator_id: u32) -> Self {
BackendError::IteratorDoesNotExist { id: iterator_id }
}
pub fn out_of_gas() -> Self {
BackendError::OutOfGas {}
}
pub fn unknown(msg: impl Into<String>) -> Self {
BackendError::Unknown { msg: msg.into() }
}
pub fn user_err(msg: impl Into<String>) -> Self {
BackendError::UserErr { msg: msg.into() }
}
}
impl From<FromUtf8Error> for BackendError {
fn from(_original: FromUtf8Error) -> Self {
BackendError::InvalidUtf8 {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gas_info_with_cost_works() {
let gas_info = GasInfo::with_cost(21);
assert_eq!(gas_info.cost, 21);
assert_eq!(gas_info.externally_used, 0);
}
#[test]
fn gas_info_with_externally_used_works() {
let gas_info = GasInfo::with_externally_used(65);
assert_eq!(gas_info.cost, 0);
assert_eq!(gas_info.externally_used, 65);
}
#[test]
fn gas_info_free_works() {
let gas_info = GasInfo::free();
assert_eq!(gas_info.cost, 0);
assert_eq!(gas_info.externally_used, 0);
}
#[test]
fn gas_info_implements_add_assign() {
let mut a = GasInfo::new(0, 0);
a += GasInfo::new(0, 0);
assert_eq!(
a,
GasInfo {
cost: 0,
externally_used: 0
}
);
let mut a = GasInfo::new(0, 0);
a += GasInfo::new(12, 0);
assert_eq!(
a,
GasInfo {
cost: 12,
externally_used: 0
}
);
let mut a = GasInfo::new(10, 0);
a += GasInfo::new(3, 0);
assert_eq!(
a,
GasInfo {
cost: 13,
externally_used: 0
}
);
let mut a = GasInfo::new(0, 0);
a += GasInfo::new(0, 7);
assert_eq!(
a,
GasInfo {
cost: 0,
externally_used: 7
}
);
let mut a = GasInfo::new(0, 8);
a += GasInfo::new(0, 9);
assert_eq!(
a,
GasInfo {
cost: 0,
externally_used: 17
}
);
let mut a = GasInfo::new(100, 200);
a += GasInfo::new(1, 2);
assert_eq!(
a,
GasInfo {
cost: 101,
externally_used: 202
}
);
}
#[test]
fn backend_err_foreign_panic() {
let error = BackendError::foreign_panic();
match error {
BackendError::ForeignPanic { .. } => {}
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn backend_err_bad_argument() {
let error = BackendError::bad_argument();
match error {
BackendError::BadArgument { .. } => {}
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn iterator_does_not_exist_works() {
let error = BackendError::iterator_does_not_exist(15);
match error {
BackendError::IteratorDoesNotExist { id, .. } => assert_eq!(id, 15),
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn backend_err_out_of_gas() {
let error = BackendError::out_of_gas();
match error {
BackendError::OutOfGas { .. } => {}
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn backend_err_unknown() {
let error = BackendError::unknown("broken");
match error {
BackendError::Unknown { msg, .. } => assert_eq!(msg, "broken"),
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn backend_err_user_err() {
let error = BackendError::user_err("invalid input");
match error {
BackendError::UserErr { msg, .. } => assert_eq!(msg, "invalid input"),
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn convert_from_fromutf8error() {
let error: BackendError = String::from_utf8(vec![0x80]).unwrap_err().into();
match error {
BackendError::InvalidUtf8 { .. } => {}
e => panic!("Unexpected error: {e:?}"),
}
}
}