#![cfg_attr(not(feature = "std"), no_std)]
#![allow(deprecated)]
use codec::{Decode, Encode};
use frame_support::{
decl_error, decl_event, decl_module, decl_storage,
dispatch::DispatchResult,
ensure,
traits::Get,
weights::{DispatchClass, FunctionOf, Pays, Weight},
StorageMap,
};
use frame_system::{self as system, ensure_signed};
use ovmi::executor::ExecError;
pub type ExecResult<T> = Result<Vec<u8>, ExecError<<T as frame_system::Trait>::AccountId>>;
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
use sp_core::crypto::UncheckedFrom;
use sp_runtime::{
traits::{Hash, Zero},
RuntimeDebug,
};
use sp_std::marker::PhantomData;
use sp_std::{collections::btree_map::BTreeMap, prelude::*, rc::Rc, vec::Vec};
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub mod predicate;
pub mod traits;
use predicate::{ExecutionContext, PredicateLoader, PredicateOvm};
use traits::{Ext, NewCallContext, PredicateAddressFor};
#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq)]
pub struct PredicateContract<CodeHash> {
pub predicate_hash: CodeHash,
pub inputs: Vec<u8>,
}
#[derive(Encode, Decode, Clone, Default, RuntimeDebug, PartialEq, Eq)]
pub struct Property<AccountId> {
pub predicate_address: AccountId,
pub inputs: Vec<Vec<u8>>,
}
#[derive(Encode, Decode, RuntimeDebug, PartialEq, Eq)]
pub enum Decision {
Undecided,
True,
False,
}
#[derive(Encode, Decode, RuntimeDebug, PartialEq, Eq)]
pub struct ChallengeGame<Hash, BlockNumber> {
property_hash: Hash,
challenges: Vec<Hash>,
decision: Decision,
created_block: BlockNumber,
}
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)]
pub struct Schedule {
pub version: u32,
pub put_code_per_byte_cost: Weight,
}
const OVM_INSTRUCTION_COST: Weight = 500_000;
impl Default for Schedule {
fn default() -> Schedule {
Schedule {
version: 0,
put_code_per_byte_cost: OVM_INSTRUCTION_COST,
}
}
}
pub struct Config {
pub schedule: Schedule,
pub max_depth: u32,
}
impl Config {
fn preload<T: Trait>() -> Config {
Config {
schedule: <Module<T>>::current_schedule(),
max_depth: T::MaxDepth::get(),
}
}
}
pub struct AtomicPredicateIdConfig<AccountId, Hash> {
pub not_address: AccountId,
pub and_address: AccountId,
pub or_address: AccountId,
pub for_all_address: AccountId,
pub there_exists_address: AccountId,
pub equal_address: AccountId,
pub is_contained_address: AccountId,
pub is_less_address: AccountId,
pub is_stored_address: AccountId,
pub is_valid_signature_address: AccountId,
pub verify_inclusion_address: AccountId,
pub secp256k1: Hash,
}
pub struct SimpleAddressDeterminer<T: Trait>(PhantomData<T>);
impl<T: Trait> PredicateAddressFor<T::Hash, T::AccountId> for SimpleAddressDeterminer<T>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
fn predicate_address_for(
code_hash: &T::Hash,
data: &[u8],
origin: &T::AccountId,
) -> T::AccountId {
let data_hash = T::Hashing::hash(data);
let mut buf = Vec::new();
buf.extend_from_slice(code_hash.as_ref());
buf.extend_from_slice(data_hash.as_ref());
buf.extend_from_slice(origin.as_ref());
UncheckedFrom::unchecked_from(T::Hashing::hash(&buf[..]))
}
}
type PredicateHash<T> = <T as system::Trait>::Hash;
type ChallengeGameOf<T> =
ChallengeGame<<T as system::Trait>::Hash, <T as system::Trait>::BlockNumber>;
pub type PropertyOf<T> = Property<<T as system::Trait>::AccountId>;
type AccountIdOf<T> = <T as frame_system::Trait>::AccountId;
type PredicateContractOf<T> = PredicateContract<<T as frame_system::Trait>::Hash>;
pub trait Trait: system::Trait {
type MaxDepth: Get<u32>;
type DisputePeriod: Get<Self::BlockNumber>;
type DeterminePredicateAddress: PredicateAddressFor<PredicateHash<Self>, Self::AccountId>;
type HashingL2: Hash<Output = Self::Hash>;
type ExternalCall: Ext<Self> + NewCallContext<Self>;
type AtomicPredicateIdConfig: Get<AtomicPredicateIdConfig<Self::AccountId, Self::Hash>>;
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}
decl_storage! {
trait Store for Module<T: Trait> as Ovm {
pub CurrentSchedule get(fn current_schedule) config(): Schedule = Schedule::default();
pub PredicateCodes get(fn predicate_codes): map hasher(identity) PredicateHash<T> => Option<Vec<u8>>;
pub PredicateCache get(fn predicate_cache): map hasher(identity) PredicateHash<T> => Option<predicate::PrefabOvmModule>;
pub Predicates get(fn predicates): map hasher(blake2_128_concat)
T::AccountId => Option<PredicateContractOf<T>>;
pub Games get(fn games): map hasher(blake2_128_concat) T::Hash => Option<ChallengeGameOf<T>>;
}
}
decl_event!(
pub enum Event<T>
where
AccountId = <T as system::Trait>::AccountId,
Property = PropertyOf<T>,
Hash = <T as system::Trait>::Hash,
BlockNumber = <T as system::Trait>::BlockNumber,
{
PutPredicate(Hash),
InstantiatePredicate(AccountId),
PropertyClaimed(Hash, Property, BlockNumber),
PropertyChallenged(Hash, Hash),
PropertyDecided(Hash, bool),
ChallengeRemoved(Hash, Hash),
}
);
decl_error! {
pub enum Error for Module<T: Trait> {
DoesNotExistGame,
MustBeCalledFromPredicate,
OutOfRangeOfChallenges,
GameIsAlradyStarted,
PropertyIsNotClaimed,
ChallengeIsAlreadyStarted,
ChallengeIsNotInTheChallengeList,
ChallengePropertyIsNotDecidedToFalse,
ChallengeListIsNotEmpty,
DisputePeriodHasNotBeenPassed,
UndecidedChallengeExists,
}
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
const DisputePeriod: <T as system::Trait>::BlockNumber = T::DisputePeriod::get();
type Error = Error<T>;
fn deposit_event() = default;
fn on_runtime_upgrade() -> Weight {
migrate::<T>();
T::MaximumBlockWeight::get()
}
#[weight = FunctionOf(
|args: (&Vec<u8>,)| Module::<T>::calc_code_put_costs(args.0),
DispatchClass::Normal,
Pays::Yes
)]
pub fn put_code(
origin,
predicate: Vec<u8>
) -> DispatchResult {
let _ = ensure_signed(origin)?;
let schedule = Self::current_schedule();
match predicate::save_code::<T>(predicate, &schedule) {
Ok(predicate_hash) => {
Self::deposit_event(RawEvent::PutPredicate(predicate_hash));
},
Err(err) => return Err(err.into()),
}
Ok(())
}
#[weight = 100_000]
pub fn instantiate(origin, predicate_hash: PredicateHash<T>, inputs: Vec<u8>) {
let origin = ensure_signed(origin)?;
let predicate_address = T::DeterminePredicateAddress::predicate_address_for(
&predicate_hash,
&inputs,
&origin
);
let predicate_contract = PredicateContract {
predicate_hash,
inputs,
};
<Predicates<T>>::insert(&predicate_address, predicate_contract);
Self::deposit_event(RawEvent::InstantiatePredicate(predicate_address));
}
#[weight = 100_000]
pub fn claim(origin, claim: PropertyOf<T>) {
let origin = ensure_signed(origin)?;
Self::only_from_dispute_contract(&origin, &claim)?;
let game_id = Self::get_property_id(&claim);
ensure!(
Self::started(&game_id),
Error::<T>::GameIsAlradyStarted,
);
let game = Self::create_game(game_id);
<Games<T>>::insert(game_id, game);
Self::deposit_event(RawEvent::PropertyClaimed(game_id, claim, Self::block_number()));
}
#[weight = 100_000]
pub fn challenge(origin, property: PropertyOf<T>, challenge_property: PropertyOf<T>) {
let origin = ensure_signed(origin)?;
Self::only_from_dispute_contract(&origin, &property)?;
let id = Self::get_property_id(&property);
ensure!(
Self::started(&id),
Error::<T>::PropertyIsNotClaimed,
);
let challenging_game_id = Self::get_property_id(&challenge_property);
ensure!(
Self::started(&challenging_game_id),
Error::<T>::ChallengeIsAlreadyStarted,
);
let challenge_game = Self::create_game(challenging_game_id);
<Games<T>>::insert(challenging_game_id, challenge_game);
let mut game = Self::games(&id).ok_or(Error::<T>::DoesNotExistGame)?;
game.challenges.push(challenging_game_id);
<Games<T>>::insert(id, game);
Self::deposit_event(RawEvent::PropertyChallenged(id, challenging_game_id));
}
#[weight = 100_000]
pub fn remove_challenge(origin, property: PropertyOf<T>, challenge_property: PropertyOf<T>) {
let origin = ensure_signed(origin)?;
Self::only_from_dispute_contract(&origin, &property)?;
let id = Self::get_property_id(&property);
ensure!(
Self::started(&id),
Error::<T>::PropertyIsNotClaimed,
);
let challenging_game_id = Self::get_property_id(&property);
ensure!(
Self::started(&challenging_game_id),
Error::<T>::ChallengeIsAlreadyStarted,
);
let mut game = Self::games(&id).ok_or(Error::<T>::DoesNotExistGame)?;
let _ = Self::find_index(&game.challenges, &challenging_game_id)
.ok_or(Error::<T>::ChallengeIsNotInTheChallengeList)?;
let challenge_game = Self::games(&challenging_game_id).ok_or(Error::<T>::DoesNotExistGame)?;
ensure!(
challenge_game.decision == Decision::False,
Error::<T>::ChallengePropertyIsNotDecidedToFalse,
);
game.challenges = game
.challenges
.into_iter()
.filter(|challenge| challenge != &challenging_game_id)
.collect();
<Games<T>>::insert(id, game);
Self::deposit_event(RawEvent::ChallengeRemoved(id, challenging_game_id));
}
#[weight = 100_000]
pub fn set_game_result(origin, property: PropertyOf<T>, result: bool)
{
let origin = ensure_signed(origin)?;
Self::only_from_dispute_contract(&origin, &property)?;
let id = Self::get_property_id(&property);
ensure!(
Self::started(&id),
Error::<T>::PropertyIsNotClaimed,
);
let mut game = Self::games(&id).ok_or(Error::<T>::DoesNotExistGame)?;
ensure!(
game.challenges.len() == 0,
Error::<T>::ChallengeListIsNotEmpty,
);
game.decision = Self::get_decision(result);
Self::deposit_event(RawEvent::PropertyDecided(id, result));
}
#[weight = 100_000]
pub fn settle_game(origin, property: PropertyOf<T>)
{
let origin = ensure_signed(origin)?;
Self::only_from_dispute_contract(&origin, &property)?;
let id = Self::get_property_id(&property);
ensure!(
Self::started(&id),
Error::<T>::PropertyIsNotClaimed,
);
let mut game = Self::games(&id).ok_or(Error::<T>::DoesNotExistGame)?;
ensure!(
game.created_block < Self::block_number() - T::DisputePeriod::get(),
Error::<T>::DisputePeriodHasNotBeenPassed,
);
for challenge in game.challenges.iter() {
let decision = Self::get_game(challenge).ok_or(Error::<T>::DoesNotExistGame)?.decision;
if decision == Decision::True {
game.decision = Decision::False;
Self::deposit_event(RawEvent::PropertyDecided(id, false));
return Ok(());
}
ensure!(
decision == Decision::Undecided,
Error::<T>::UndecidedChallengeExists,
);
}
game.decision = Decision::True;
<Games<T>>::insert(id, game);
Self::deposit_event(RawEvent::PropertyDecided(id, true));
}
}
}
fn migrate<T: Trait>() {
}
impl<T: Trait> Module<T> {
fn calc_code_put_costs(code: &Vec<u8>) -> Weight {
<Module<T>>::current_schedule()
.put_code_per_byte_cost
.saturating_mul(code.len() as Weight)
}
pub fn bare_call(
origin: T::AccountId,
dest: T::AccountId,
input_data: Vec<u8>,
) -> ExecResult<T> {
Self::execute_ovm(origin, |ctx| ctx.call(dest, input_data))
}
fn execute_ovm(
origin: T::AccountId,
func: impl FnOnce(&mut ExecutionContext<T>) -> ExecResult<T>,
) -> ExecResult<T> {
let cfg = Rc::new(Config::preload::<T>());
let schedule = Rc::new(cfg.schedule.clone());
let vm = Rc::new(PredicateOvm::new(Rc::clone(&schedule)));
let loader = Rc::new(PredicateLoader::new(Rc::clone(&schedule)));
let mut ctx = ExecutionContext::top_level(origin.clone(), cfg, vm, loader);
func(&mut ctx)
}
pub fn is_decided(property: &PropertyOf<T>) -> Decision {
let game = match Self::games(Self::get_property_id(property)) {
Some(game) => game,
None => return Decision::Undecided,
};
game.decision
}
pub fn is_decided_by_id(id: T::Hash) -> Decision {
let game = match Self::games(&id) {
Some(game) => game,
None => return Decision::Undecided,
};
game.decision
}
pub fn get_game(claim_id: &T::Hash) -> Option<ChallengeGameOf<T>> {
Self::games(claim_id)
}
pub fn get_property_id(property: &PropertyOf<T>) -> T::Hash {
T::Hashing::hash_of(property)
}
pub fn is_challenge_of(property: &PropertyOf<T>, challenge_property: &PropertyOf<T>) -> bool {
if let Some(game) = Self::get_game(&Self::get_property_id(property)) {
if let Some(_) =
Self::find_index(&game.challenges, &Self::get_property_id(challenge_property))
{
return true;
}
}
false
}
pub fn started(id: &T::Hash) -> bool {
if let Some(game) = Self::games(id) {
game.created_block != <T as system::Trait>::BlockNumber::zero()
} else {
false
}
}
pub fn block_number() -> <T as system::Trait>::BlockNumber {
<system::Module<T>>::block_number()
}
pub fn is_decidable(property_id: &T::Hash) -> bool {
let game = match Self::games(property_id) {
Some(game) => game,
None => return false,
};
if game.created_block > Self::block_number() - T::DisputePeriod::get() {
return false;
}
game.challenges.iter().all(|challenge| {
if let Some(challenging_game) = Self::games(challenge) {
return challenging_game.decision == Decision::False;
}
false
})
}
fn create_game(id: T::Hash) -> ChallengeGameOf<T> {
ChallengeGame {
property_hash: id,
challenges: vec![],
decision: Decision::Undecided,
created_block: Self::block_number(),
}
}
fn get_decision(result: bool) -> Decision {
if result {
return Decision::True;
}
Decision::False
}
fn find_index<Hash: PartialEq>(array: &Vec<Hash>, item: &Hash) -> Option<usize> {
array.iter().position(|hash| hash == item)
}
fn only_from_dispute_contract(
origin: &T::AccountId,
property: &PropertyOf<T>,
) -> DispatchResult {
ensure!(
&property.predicate_address == origin,
Error::<T>::MustBeCalledFromPredicate,
);
Ok(())
}
}