Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: Pi-Squared-Inc/wasm-semantics
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 095a0f8882c726ddb0e3e1a9cd4ad8ad22e44831
Choose a base ref
..
head repository: Pi-Squared-Inc/wasm-semantics
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 2933f59d45a91ef3581029ad170cd0feabb0b137
Choose a head ref
4 changes: 2 additions & 2 deletions tests/ulm/erc20/rust/src/assertions.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@

use crate::ulm_hooks;
use crate::ulm;

pub fn fail(msg: &str) -> ! {
ulm_hooks::failWrapper(msg);
ulm::failWrapper(msg);
}

#[macro_export]
2 changes: 1 addition & 1 deletion tests/ulm/erc20/rust/src/decoder.rs
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
// let (value_n, decoder) = decoder.decode();
//
// At the end, it's good practice to check that you have decoded everything
// you wanted. The following will not compile if decoding didn't finish.
// you wanted. The following will not compile if decoding didn't finish.
//
// decoder.check_done();

2 changes: 1 addition & 1 deletion tests/ulm/erc20/rust/src/encoder.rs
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ pub trait Encodable {
impl Encodable for String {
fn encode(&self) -> (EncodingType, Bytes) {
let bytes = self.as_bytes();
let total_bytes_length = 32 + ((bytes.len() + 31) / 32) * 32;
let total_bytes_length = 32 + ((bytes.len() + 31) / 32) * 32;
let mut result = BytesMut::with_capacity(total_bytes_length);
let (_, len_bytes) = U256::from_u64(bytes.len() as u64).encode();

4 changes: 3 additions & 1 deletion tests/ulm/erc20/rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -2,8 +2,10 @@ mod assertions;
mod decoder;
mod encoder;
mod predicate;
mod storage;
mod unsigned;
mod ulm_hooks;
mod ulm;

mod encoding_tests;
mod storage_tests;
mod unsigned_tests;
106 changes: 106 additions & 0 deletions tests/ulm/erc20/rust/src/storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SingleChunkStorage is a class which makes it easy to work with storage
// for objects that fit in 32 bytes. Accessing storage will crash (fail) if
// the stored bytes cannot be converted to the value type.
//
// Let's say you want to access a storage object under the name N, with
// key (K1, K2, ..., Kn) and with type T. You need the following:
// * K1 ..., Kn must implement Encodable
// * T must implement TryFrom<U256> and Into<U256>
//
// Then you can build the storage object like this:
//
// let mut builder = SingleChunkStorageBuilder::<MyValueType>::new(api, hooks_api, &("Storage name".to_string()))
// builder.add_arg(K1);
// builder.add_arg(K2);
// ...
// builder.add_arg(Kn);
// let mut storage = builder.build();
//
// In order to set the storage value, do this:
//
// storage.set(&my_value);
//
// In order to get the stored value, do this:
//
// let my_value = storage.get();

use core::marker::PhantomData;
use std::cell::RefCell;
use std::convert::TryFrom;
use std::convert::Into;
use std::rc::Rc;

use crate::assertions::fail;
use crate::encoder::Encodable;
use crate::encoder::Encoder;
use crate::unsigned::U256;
use crate::ulm;

pub struct SingleChunkStorage<'a, ValueType>
where
ValueType: Into<U256> + TryFrom<U256>,
{
phantom_value: PhantomData<&'a ValueType>,
api: Rc<RefCell<dyn ulm::Ulm>>,
fingerprint: U256,
}

impl<'a, ValueType> SingleChunkStorage<'a, ValueType>
where
ValueType: Into<U256> + TryFrom<U256>,
{
pub fn new(api: Rc<RefCell<dyn ulm::Ulm>>, fingerprint: U256) -> Self {
SingleChunkStorage::<ValueType> { phantom_value: PhantomData, api, fingerprint }
}

pub fn set(&mut self, value: ValueType) {
let converted: U256 = value.into();
ulm::set_account_storage(&mut *self.api.borrow_mut(), &self.fingerprint, &converted);
}

pub fn get(&self) -> ValueType {
let u256 = ulm::get_account_storage(&*self.api.borrow(), &self.fingerprint);
match u256.try_into() {
Ok(v) => v,
Err(_) => fail("Conversion from U256 failed for storage"),
}
}
}

pub struct SingleChunkStorageBuilder<'a, ValueType>
where
ValueType: Into<U256> + TryFrom<U256>,
{
phantom_value: PhantomData<&'a ValueType>,
api: Rc<RefCell<dyn ulm::Ulm>>,
encoder: Encoder,
}

impl<'a, ValueType> SingleChunkStorageBuilder<'a, ValueType>
where
ValueType: Into<U256> + TryFrom<U256>,
{
pub fn new(api: Rc<RefCell<dyn ulm::Ulm>>, name: &String) -> Self {
let mut encoder = Encoder::new();
encoder.add(name);
Self::from_encoder(api, encoder)
}

fn from_encoder(api: Rc<RefCell<dyn ulm::Ulm>>, encoder: Encoder) -> Self {
SingleChunkStorageBuilder::<ValueType> {
phantom_value: PhantomData,
api,
encoder,
}
}

pub fn add_arg(&mut self, arg: &dyn Encodable) {
self.encoder.add(arg);
}

pub fn build(&mut self) -> SingleChunkStorage<ValueType> {
let bytes = self.encoder.encode();
let fingerprint = ulm::keccak_hash_int(&*self.api.borrow(), &bytes);
SingleChunkStorage::new(self.api.clone(), fingerprint)
}
}
96 changes: 96 additions & 0 deletions tests/ulm/erc20/rust/src/storage_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#[cfg(test)]
mod encoding_tests {
use crate::storage::*;
use crate::ulm;
use crate::unsigned::*;

#[test]
fn read_value_not_set() {
let api = ulm::mock::UlmMock::new();
let mut builder = SingleChunkStorageBuilder::<U256>::new(api, &("my_storage".to_string()));

let storage = builder.build();
let value: U256 = storage.get();

assert_eq!(U256::from_u64(0), value);
}

#[test]
fn write_read_u256() {
let api = ulm::mock::UlmMock::new();
let mut builder = SingleChunkStorageBuilder::<U256>::new(api, &("my_storage".to_string()));

let mut storage = builder.build();
storage.set(U256::from_u64(123456789));
let value: U256 = storage.get();

assert_eq!(U256::from_u64(123456789), value);
}

#[test]
fn write_read_u8() {
let api = ulm::mock::UlmMock::new();
let mut builder = SingleChunkStorageBuilder::<Unsigned::<1>>::new(api, &("my_storage".to_string()));

let mut storage = builder.build();
storage.set(Unsigned::<1>::from_u64(123));
let value: Unsigned<1> = storage.get();

assert_eq!(Unsigned::<1>::from_u64(123), value);
}

#[test]
fn write_read_args() {
let api = ulm::mock::UlmMock::new();

let mut builder = SingleChunkStorageBuilder::<U256>::new(api, &("my_storage".to_string()));

builder.add_arg(&U256::from_u64(5));

let mut storage = builder.build();
storage.set(U256::from_u64(123456789));
let value: U256 = storage.get();

assert_eq!(U256::from_u64(123456789), value);
}

#[test]
fn no_confusion() {
let api = ulm::mock::UlmMock::new();

let mut builder1 = SingleChunkStorageBuilder::<U256>::new(api.clone(), &("my_storage".to_string()));
let mut builder2 = SingleChunkStorageBuilder::<U256>::new(api.clone(), &("my_storage1".to_string()));
let mut builder3 = SingleChunkStorageBuilder::<U256>::new(api.clone(), &("my_storage".to_string()));
let mut builder4 = SingleChunkStorageBuilder::<U256>::new(api.clone(), &("my_storage".to_string()));
let mut builder5 = SingleChunkStorageBuilder::<U256>::new(api, &("my_storage".to_string()));

builder3.add_arg(&U256::from_u64(3));
builder4.add_arg(&U256::from_u64(4));
builder5.add_arg(&U256::from_u64(3));
builder5.add_arg(&U256::from_u64(4));

let mut storage1 = builder1.build();
let mut storage2 = builder2.build();
let mut storage3 = builder3.build();
let mut storage4 = builder4.build();
let mut storage5 = builder5.build();

storage1.set(U256::from_u64(1));
storage2.set(U256::from_u64(2));
storage3.set(U256::from_u64(3));
storage4.set(U256::from_u64(4));
storage5.set(U256::from_u64(5));

let value1: U256 = storage1.get();
let value2: U256 = storage2.get();
let value3: U256 = storage3.get();
let value4: U256 = storage4.get();
let value5: U256 = storage5.get();

assert_eq!(U256::from_u64(1), value1);
assert_eq!(U256::from_u64(2), value2);
assert_eq!(U256::from_u64(3), value3);
assert_eq!(U256::from_u64(4), value4);
assert_eq!(U256::from_u64(5), value5);
}
}
153 changes: 153 additions & 0 deletions tests/ulm/erc20/rust/src/ulm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
use bytes::{Bytes, Buf};

use crate::unsigned::U256;

#[cfg(not(test))]
extern "C" {
// key and value must have a length of exactly 32.
#[allow(non_snake_case)]
pub fn GetAccountStorage(key: *const u8, value: *mut u8);

// key and value must have a length of exactly 32.
#[allow(non_snake_case)]
pub fn SetAccountStorage(key: *const u8, value: *const u8);

#[allow(dead_code)]
pub fn fail(msg: *const u8, msg_len: usize) -> !;

// result must have a length of exactly 32.
pub fn keccakHash(msg: *const u8, msg_len: usize, result: *mut u8);

}

#[cfg(test)]
pub mod overrides {
#[no_mangle]
pub extern "C" fn fail(_msg: *const u8, _msg_len: usize) -> ! {
panic!("fail called");
}
}

#[cfg(test)]
#[allow(non_snake_case)]
pub fn failWrapper(msg: &str) -> ! {
panic!("{}", msg);
}

#[cfg(not(test))]
#[allow(non_snake_case)]
pub fn failWrapper(msg: &str) -> ! {
let msg_bytes = msg.as_bytes();
unsafe { fail(msg_bytes.as_ptr(), msg_bytes.len()); }
}

pub trait Ulm {
fn get_account_storage(&self, key: &[u8; 32], value: &mut [u8; 32]);
fn set_account_storage(&mut self, key: &[u8; 32], value: &[u8; 32]);

fn keccak_hash(&self, value: &[u8], result: &mut [u8; 32]);
}

#[cfg(not(test))]
struct UlmImpl {}

#[cfg(not(test))]
impl Ulm for UlmImpl {
fn get_account_storage(&self, key: &[u8; 32], value: &mut [u8; 32]) {
unsafe { GetAccountStorage(key.as_ptr(), value.as_mut_ptr()); }
}

fn set_account_storage(&mut self, key: &[u8; 32], value: &[u8; 32]) {
unsafe { SetAccountStorage(key.as_ptr(), value.as_ptr()); }
}

fn keccak_hash(&self, value: &[u8], result: &mut [u8; 32]) {
unsafe { keccakHash(value.as_ptr(), value.len(), result.as_mut_ptr()); }
}
}

#[cfg(test)]
pub mod mock {
use bytes::{Bytes, Buf};
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;

use crate::assertions::fail;
use crate::require;
use crate::ulm::Ulm;

pub struct UlmMock {
storage: HashMap<Bytes, Bytes>,
}

impl UlmMock {
pub fn new() -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(UlmMock { storage: HashMap::new() }))
}
}

impl Ulm for UlmMock {
fn get_account_storage(&self, key: &[u8; 32], value: &mut [u8; 32]) {
let bytes_key = Bytes::copy_from_slice(key);
match self.storage.get(&bytes_key) {
Some(v) => {
let bytes_value = v.chunk();
require!(bytes_value.len() == 32, "unexpected value length in storage");
value.copy_from_slice(bytes_value);
},
None => {
for i in 0 .. value.len() {
value[i] = 0;
}
},
}
}

fn set_account_storage(&mut self, key: &[u8; 32], value: &[u8; 32]) {
let bytes_key = Bytes::copy_from_slice(key);
let bytes_value = Bytes::copy_from_slice(value);
self.storage.insert(bytes_key, bytes_value);
}

fn keccak_hash(&self, value: &[u8], result: &mut [u8; 32]) {
for i in 1 .. result.len() {
result[i] = 0;
}
for i in 1 .. value.len() {
result[i % 32] ^= value[i];
}
}
}
}

pub fn get_account_storage(api: &dyn Ulm, key: &U256) -> U256 {
let mut key_bytes = [0_u8; 32];
key.copy_to_array_le(&mut key_bytes);

let mut value_bytes = [0_u8; 32];
api.get_account_storage(&key_bytes, &mut value_bytes);

U256::from_array_le(value_bytes)
}

pub fn set_account_storage(api: &mut dyn Ulm, key: &U256, value: &U256) {
let mut key_bytes = [0_u8; 32];
key.copy_to_array_le(&mut key_bytes);

let mut value_bytes = [0_u8; 32];
value.copy_to_array_le(&mut value_bytes);

api.set_account_storage(&key_bytes, &value_bytes);
}

pub fn keccak_hash(api: &dyn Ulm, value: &Bytes) -> [u8; 32] {
let mut fingerprint = [0_u8; 32];
api.keccak_hash(value.chunk(), &mut fingerprint);
fingerprint
}

pub fn keccak_hash_int(api: &dyn Ulm, value: &Bytes) -> U256 {
let fingerprint = keccak_hash(api, value);
U256::from_array_le(fingerprint)
}
Loading