Commit b13410d4 authored by Hero Bird's avatar Hero Bird

initial commit

parents
# Ignore build artifacts from the local tests sub-crate.
/target/
/pdsl_tests/target/
/pdsl_core/target/
/pdsl_derive/target/
/design/
/pdsl_tests/examples/
# Ignore backup files creates by cargo fmt.
**/*.rs.bk
# Remove Cargo.lock when creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
[workspace]
members = [
"pdsl_core",
"pdsl_derive",
"pdsl_tests"
]
[profile.release]
panic = "abort"
lto = true
This diff is collapsed.
# pDSL - Parity's DSL for Smart Contracts
**IMPORTANT NOTE:** THIS IS WORK IN PROGRESS! Do not expect this to be working of final in any way.
Write WebAssembly based smart contracts in Rust operating on Substrate.
## Structure
This repository currently exists of three different sub modules.
- `pdsl_core`: Defines the core utilities and abstractions to declare, implement, test and execute smart contracts.
- `pdsl_derive`: Utilities to simplify writing smart contract code.
- `pdsl_test`: Test framework for the above.
While users can use only `pdsl_core` to write entire smart contracts it is recommended to use this library via `pdsl_derive`.
## Design Goals
- `pdsl_core`
- Safe and unsafe abstractions
- Library-only solution
- Stand alone for writing smart contracts
- Facility to test and even benchmark contracts off-chain
- No tricky-intransparent abstractions
- `pdsl_derive`
- eDSL that makes writing smart contracts using `pdsl_core` easier
- Based only on `pdsl_core`
- Empower your smart contracts by Rust's
- safety guarantees
- performance characteristics
## License
The entire code within this repository is licensed under the [GLP-v3](LICENSE). Please [contact us](https://www.parity.io/contact/) if you have questions about the licensing of our products.
[package]
name = "pdsl_core"
version = "0.1.0"
authors = ["Herobird <robbepop@web.de>"]
edition = "2018"
license = "MIT/Apache-2.0"
readme = "README.md"
# repository = "https://github.com/robbepop/substrate-contract"
# homepage = "https://github.com/robbepop/substrate-contract"
# documentation = "https://robbepop.github.io/pwasm-abi/substrate-contract/"
description = "[pDSL: Parity eDSL] Rust based eDSL for writing smart contracts for Substrate"
keywords = ["wasm", "parity", "webassembly", "blockchain", "edsl"]
categories = ["no-std", "embedded"]
include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"]
[dependencies]
parity-codec = { version = "2.0" }
parity-codec-derive = { version = "2.0" }
tiny-keccak = "1.4"
[dev-dependencies]
lazy_static = "1.2"
../LICENSE
\ No newline at end of file
../README.md
\ No newline at end of file
//! Collections operating on contract storage.
//!
//! Collection types that safely abstract from contract storage.
//! Users are recommended to use them instead of unsafe abstractions
//! such as `Key` or `Storage`.
//!
//! Currently supported data structures are:
//!
//! - `StorageVec`: Similar to Rust's `Vec`
//! - `StorageMap`: Similar to Rust's `HashMap`
//!
//! Beware that the similarities are only meant for their respective APIs.
//! Internally they are structured completely different and may even
//! exhibit different efficiency characteristics.
pub mod storage_map;
mod storage_vec;
pub use self::{
storage_vec::{
StorageVec,
},
storage_map::{
StorageMap,
},
};
use crate::storage::{Key, Synced, SyncedChunk};
use crate::hash::{self, HashAsKeccak256};
use std::borrow::Borrow;
/// Mapping stored in the contract storage.
///
/// # Note
///
/// This performs a quadratic probing on the next 2^32 slots
/// following its initial key. So it can store up to 2^32 elements in total.
///
/// Instead of storing element values (`V`) directly, it stores
/// storage map entries of `(K, V)` instead. This allows to represent
/// the storage that is associated to the storage map to be in three
/// different states.
///
/// 1. Occupied slot with key and value.
/// 2. Removed slot that was occupied before.
/// 3. Empty slot when there never was an insertion for this storage slot.
///
/// This distinction is important for the quadratic map probing.
#[derive(Debug)]
pub struct StorageMap<K, V> {
/// The storage key to the length of this storage map.
len: Synced<u32>,
/// The first half of the entry buffer is equal to the key,
/// the second half will be replaced with the respective
/// hash of any given key upon usage.
///
/// Afterwards this value is hashed again and used as key
/// into the contract storage.
entries: SyncedChunk<Entry<K, V>>,
}
/// An entry of a storage map.
///
/// This can either store the entries key and value
/// or represent an entry that was removed after it
/// has been occupied with key and value.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(parity_codec_derive::Encode, parity_codec_derive::Decode)]
pub enum Entry<K, V> {
/// An occupied slot with a key and a value.
Occupied(OccupiedEntry<K, V>),
/// A removed slot that was occupied before.
Removed,
}
/// An occupied entry of a storage map.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(parity_codec_derive::Encode, parity_codec_derive::Decode)]
pub struct OccupiedEntry<K, V> {
/// The entry's key.
key: K,
/// The entry's value.
val: V,
}
impl<K, V> From<Key> for StorageMap<K, V>
where
K: parity_codec::Codec,
V: parity_codec::Codec,
{
fn from(key: Key) -> Self {
StorageMap{
len: Synced::from(key),
entries: SyncedChunk::from(
Key::with_offset(&key, 1)
),
}
}
}
impl<K, V> StorageMap<K, V> {
/// Returns the number of key-value pairs in the map.
pub fn len(&self) -> u32 {
*self.len.get()
}
/// Returns `true` if the map contains no elements.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
/// Converts the given bytes into a `u32` value.
///
/// The first byte in the array will be the most significant byte.
fn bytes_to_u32(bytes: [u8; 4]) -> u32 {
let mut res = 0;
res |= (bytes[0] as u32) << 24;
res |= (bytes[1] as u32) << 16;
res |= (bytes[2] as u32) << 8;
res |= (bytes[3] as u32) << 0;
res
}
/// Converts the given slice into an array with fixed size of 4.
///
/// Returns `None` if the slice's length is not 4.
fn slice_as_array4<T>(bytes: &[T]) -> Option<[T; 4]>
where
T: Default + Copy
{
if bytes.len() != 4 {
return None
}
let mut array = [T::default(); 4];
for i in 0..4 {
array[i] = bytes[i];
}
Some(array)
}
impl<K, V> StorageMap<K, V>
where
K: parity_codec::Codec + HashAsKeccak256 + Eq,
V: parity_codec::Codec,
{
/// Probes for a free or usable slot.
///
/// # Note
///
/// - Uses quadratic probing.
/// - Returns `(true, _)` if there was a key-match of an already
/// occupied slot, returns `(false, _)` if the found slot is empty.
/// - Returns `(_, n)` if `n` is the found probed index.
fn probe<Q>(&self, key: &Q, inserting: bool) -> (bool, u32)
where
K: Borrow<Q>,
Q: HashAsKeccak256 + Eq
{
// Convert the first 4 bytes in the keccak256 hash
// of the key into a big-endian unsigned integer.
let probe_start = bytes_to_u32(
slice_as_array4(
&(hash::keccak256(key.borrow())[0..4])
).expect(
"[pdsl_core::StorageMap::insert] Error \
couldn't convert to probe_start byte array"
)
);
// This is the offset for the quadratic probing.
let mut probe_hops = 0;
let mut probe_offset = 0;
'outer: loop {
let probe_index = probe_start.wrapping_add(probe_offset);
match self.entries.get(probe_index) {
Some(Entry::Occupied(entry)) => {
if key == entry.key.borrow() {
return (true, probe_index)
}
// Need to jump using quadratic probing.
probe_hops += 1;
probe_offset = probe_hops * probe_hops;
continue 'outer
}
Some(Entry::Removed) | None => {
// We can insert into this slot.
if inserting {
return (false, probe_index)
}
continue 'outer
}
}
}
}
/// Probes for a free or usable slot while inserting.
///
/// # Note
///
/// For more information refer to the `fn probe` documentation.
fn probe_inserting<Q>(&self, key: &Q) -> (bool, u32)
where
K: Borrow<Q>,
Q: HashAsKeccak256 + Eq
{
self.probe(key, true)
}
/// Probes for a free or usable slot while inspecting.
///
/// # Note
///
/// For more information refer to the `fn probe` documentation.
fn probe_inspecting<Q>(&self, key: &Q) -> u32
where
K: Borrow<Q>,
Q: HashAsKeccak256 + Eq
{
self.probe(key, false).1
}
/// Inserts a key-value pair into the map.
///
/// If the map did not have this key present, `None` is returned.
///
/// If the map did have this key present, the value is updated,
/// and the old value is returned.
/// The key is not updated, though;
/// this matters for types that can be == without being identical.
/// See the module-level documentation for more.
pub fn insert(&mut self, key: K, val: V) -> Option<V> {
match self.probe_inserting(&key) {
(true, probe_index) => {
// Keys match, values might not.
// So we have to overwrite this entry with the new value.
let old = self.entries.remove(probe_index);
self.entries.insert(
probe_index, Entry::Occupied(OccupiedEntry{key, val})
);
return match old.unwrap() {
Entry::Occupied(OccupiedEntry{val, ..}) => Some(val),
Entry::Removed => None,
}
}
(false, probe_index) => {
// We can insert into this slot.
self.entries.insert(
probe_index,
Entry::Occupied(OccupiedEntry{key, val})
);
return None
}
}
}
/// Removes a key from the map,
/// returning the value at the key if the key was previously in the map.
///
/// # Note
///
/// The key may be any borrowed form of the map's key type,
/// but Hash and Eq on the borrowed form must match those for the key type.
pub fn remove<Q>(&mut self, key: &Q) -> Option<V>
where
K: Borrow<Q>,
Q: HashAsKeccak256 + Eq
{
let probe_index = self.probe_inspecting(key);
match self.entries.remove(probe_index) {
Some(Entry::Removed) | None => None,
Some(Entry::Occupied(OccupiedEntry{val, ..})) => Some(val),
}
}
/// Returns the value corresponding to the key.
///
/// The key may be any borrowed form of the map's key type,
/// but Hash and Eq on the borrowed form must match those for the key type.
pub fn get<Q>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: HashAsKeccak256 + Eq
{
match self.entry(key) {
Some(Entry::Removed) | None => None,
Some(Entry::Occupied(OccupiedEntry{val, ..})) => Some(val),
}
}
/// Returns the entry corresponding to the key.
///
/// The key may be any borrowed form of the map's key type,
/// but Hash and Eq on the borrowed form must match those for the key type.
pub fn entry<Q>(&self, key: &Q) -> Option<&Entry<K, V>>
where
K: Borrow<Q>,
Q: HashAsKeccak256 + Eq
{
self.entries.get(self.probe_inspecting(key))
}
}
use crate::storage::{
Key,
Synced,
SyncedRef,
SyncedChunk,
};
use std::marker::PhantomData;
/// A storage vector capable of storing elements in the storage
/// in contiguous hashes.
///
/// # Note
///
/// - Due to the architecture of the storage this storage vector is
/// very different from an actual vector such as Rust's `Vec`.
/// Even though its hashes with which it stores elements in the storage
/// are contiguous doesn't mean the elements are stored in a single
/// dense block of memory.
///
/// - This can be used as a building block for other storage data structures.
#[derive(Debug)]
pub struct StorageVec<T> {
/// The length of this storage vec.
len: Synced<u32>,
/// Synced chunk of elements.
synced: SyncedChunk<T>,
/// Marker to make Rust's type system happy.
marker: PhantomData<T>,
}
impl<T> From<Key> for StorageVec<T> {
fn from(key: Key) -> Self {
StorageVec{
len: Synced::from(key),
synced: SyncedChunk::from(
Key::with_offset(&key, 1)
),
marker: PhantomData,
}
}
}
impl<T> StorageVec<T> {
/// Returns the number of elements in the vector.
pub fn len(&self) -> u32 {
*self.len.get()
}
/// Returns `true` if the vector contains no elements.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<T> StorageVec<T>
where
T: parity_codec::Codec
{
/// Returns the n-th elements of this storage vec.
///
/// Returns `None` if given `n` is out of bounds.
pub fn get(&self, n: u32) -> Option<&T> {
if n >= self.len() {
return None
}
// Some(self.storage_at(n).get())
Some(self.synced.get(n).expect("TODO"))
}
/// Returns a mutable reference to the n-th element of this storage vec.
///
/// Returns `None` if given `n` is out of bounds.
pub fn get_mut(&mut self, n: u32) -> Option<SyncedRef<T>> {
if n >= self.len() {
return None
}
Some(self.get_mut(n).expect("TODO"))
}
/// Appends an element to the back of a collection.
pub fn push(&mut self, val: T) {
if self.len() == u32::max_value() {
panic!(
"[pdsl_core::StorageVec::push] \
Error: cannot push more elements than `u32::MAX`"
)
}
let last_index = self.len();
self.len.set(last_index + 1);
self.synced.insert(last_index, val)
}
/// Removes the last element from a vector and returns it, or `None` if it is empty.
pub fn pop(&mut self) -> Option<T> {
if self.len() == 0 {
return None
}
let last_index = self.len() - 1;
Some(self.synced.remove(last_index).expect("TODO"))
}
/// Removes an element from the vector and returns it.
///
/// The removed element is replaced by the last element of the vector.
/// This does not preserve ordering, but is O(1).
///
/// Returns `None` if empty or if index is out of bounds.
pub fn swap_remove(&mut self, index: u32) -> Option<T> {
if index >= self.len() {
return None
}
if self.len() <= 1 {
return self.pop()
}
let ret = self
.synced.remove(index)
.expect(
"[pdsl_core::StorageVec::swap_remove] \
Error: expected element at given index"
);
let last = self
.pop()
.expect(
"[pdsl_core::StorageVec::swap_remove] \
Error: expected element for pop()"
);
self.synced.insert(index, last);
Some(ret)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::env::{Env, TestEnv};
use parity_codec::{Encode};
#[test]
fn new() {
let k0 = Key([0x0; 32]);
{
TestEnv::store(k0.as_bytes(), &u32::encode(&0));
assert_eq!(StorageVec::<i32>::from(k0).len(), 0);
}
{
TestEnv::reset(); // Not necesarily required.
TestEnv::store(k0.as_bytes(), &u32::encode(&42));
assert_eq!(StorageVec::<i32>::from(k0).len(), 42);
}
}
}
//! Externally defined and provded functionality.
//!
//! Refer to substrate SRML for more information.
/// Refer to substrate SRML contract module for more documentation.
pub mod c_abi {
extern "C" {
pub fn ext_create(
init_code_ptr: u32,
init_code_len: u32,
gas: u64,
value_ptr: u32,
value_len: u32,
input_data_ptr: u32,
input_data_len: u32
) -> u32;
pub fn ext_call(
callee_ptr: u32,
callee_len: u32,
gas: u64,
value_ptr: u32,
value_len: u32,
input_data_ptr: u32,
input_data_len: u32
) -> u32;
pub fn ext_set_storage(key_ptr: u32, value_non_null: u32, value_ptr: u32, value_len: u32);
pub fn ext_get_storage(key_ptr: u32) -> u32;
pub fn ext_scratch_size() -> u32;
pub fn ext_scratch_copy(dest_ptr: u32, offset: u32, len: u32);
pub fn ext_input_size() -> u32;
pub fn ext_input_copy(dest_ptr: u32, offset: u32, len: u32);
pub fn ext_return(data_ptr: u32, data_len: u32) -> !;
}
}
/// The evironment API usable by SRML contracts.
pub trait Env {
/// Stores the given value under the given key.
fn store(key: &[u8], value: &[u8]);
/// Clears the value stored under the given key.
fn clear(key: &[u8]);
/// Loads data stored under the given key.
fn load(key: &[u8]) -> Option<Vec<u8>>;
/// Loads input data for contract execution.
fn input() -> Vec<u8>;
/// Returns from the contract execution with the given value.
fn return_(value: &[u8]) -> !;
}
#[cfg(not(test))]
mod default {
use super::*;
/// The default SRML contracts environment.
pub struct DefaultEnv;
impl Env for DefaultEnv {
fn store(key: &[u8], value: &[u8]) {
unsafe {
c_abi::ext_set_storage(
key.as_ptr() as u32,
1,
value.as_ptr() as u32,
value.len() as u32
);
}
}
fn clear(key: &[u8]) {
unsafe {
c_abi::ext_set_storage(key.as_ptr() as u32, 0, 0, 0)
}
}
fn load(key: &[u8]) -> Option<Vec<u8>> {
const SUCCESS: u32 = 0;
let result = unsafe { c_abi::ext_get_storage(key.as_ptr() as u32) };