#![cfg_attr(not(feature = "std"), no_std)]
mod mock;
pub mod reinit_extension;
mod tests;
pub use eq_oracle;
use eq_bailsman::{BailsmanManager, LtvChecker};
use eq_balances::{BalanceGetter, SignedBalance};
use eq_primitives::{currency::Currency, FeeManager};
use eq_treasury::EqBuyout;
use eq_utils::{eq_ensure, log::eq_log, ok_or_error};
use frame_support::{
codec::{Decode, Encode},
debug, decl_error, decl_event, decl_module, decl_storage,
dispatch::{DispatchError, DispatchResult},
traits::{Get, OnKilledAccount, OnNewAccount, UnixTime},
Parameter,
};
use sp_application_crypto::RuntimeAppPublic;
use sp_arithmetic::{FixedI128, FixedI64, FixedPointNumber};
use sp_core::crypto::KeyTypeId;
use sp_runtime::{
offchain::storage::StorageValueRef,
traits::{AccountIdConversion, AtLeast32Bit, MaybeSerializeDeserialize, Member, Zero},
transaction_validity::{
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
ValidTransaction,
},
ModuleId, RuntimeDebug,
};
use sp_std::convert::TryInto;
use sp_std::prelude::*;
use system as frame_system;
use system::offchain::{SendTransactionTypes, SubmitTransaction};
use system::{ensure_none, ensure_root, ensure_signed};
pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"rate");
const DB_PREFIX: &[u8] = b"eq-rate/";
pub type AuthIndex = u32;
type OffchainResult<A> = Result<A, OffchainErr>;
pub mod ed25519 {
pub use super::KEY_TYPE;
mod app_ed25519 {
use sp_application_crypto::{app_crypto, ed25519};
app_crypto!(ed25519, super::KEY_TYPE);
}
sp_application_crypto::with_pair! {
pub type AuthorityPair = app_ed25519::Pair;
}
pub type AuthoritySignature = app_ed25519::Signature;
pub type AuthorityId = app_ed25519::Public;
}
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct ReinitRequest<AccountId, BlockNumber>
where
AccountId: PartialEq + Eq + Decode + Encode,
BlockNumber: Decode + Encode,
{
pub account: AccountId,
pub authority_index: AuthIndex,
pub validators_len: u32,
pub block_num: BlockNumber,
}
pub trait Trait: SendTransactionTypes<Call<Self>> + pallet_session::Trait {
type UnixTime: UnixTime;
type Balance: Member
+ AtLeast32Bit
+ MaybeSerializeDeserialize
+ Parameter
+ Default
+ From<u64>
+ Into<u64>;
type BalanceGetter: eq_balances::BalanceGetter<Self::AccountId, Self::Balance>;
type AuthorityId: Member + Parameter + RuntimeAppPublic + Default + Ord;
type BailsmanManager: BailsmanManager<Self::AccountId>;
type LtvChecker: eq_bailsman::LtvChecker<Self::AccountId, Self::Balance>;
type MinSurplus: Get<Self::Balance>;
type MinTempBailsman: Get<Self::Balance>;
type PriceGetter: eq_oracle::PriceGetter;
type UnsignedPriority: Get<TransactionPriority>;
type FeeManager: FeeManager<Self::AccountId, Self::Balance>;
type EqBuyout: EqBuyout<Self::AccountId, Self::Balance>;
type BailsmanModuleId: Get<ModuleId>;
}
decl_storage! {
trait Store for Module<T: Trait> as EqRate {
Keys get(fn keys): Vec<T::AuthorityId>;
pub LastFeeUpdate get (fn last_fee_update): map hasher(blake2_128_concat) T::AccountId => u64;
pub NowMillisOffset get(fn now_millis_offset): u64;
}
add_extra_genesis {
config(keys): Vec<T::AuthorityId>;
build(|config| Module::<T>::initialize_keys(&config.keys))
}
}
decl_error! {
pub enum Error for Module<T: Trait> {
InvalidOffset,
}
}
#[allow(dead_code)]
enum OffchainErr {
FailedSigning,
SubmitTransaction,
}
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>;
#[weight = 10_000]
pub fn reinit(origin,
request: ReinitRequest<T::AccountId, T::BlockNumber>,
_signature: <T::AuthorityId as RuntimeAppPublic>::Signature) {
ensure_none(origin)?;
eq_log!(
"reinit for {:?} by {:?} with len {}",
request.account,
request.authority_index,
request.validators_len);
#[allow(unused_must_use)]{
Self::_reinit(&request.account);
}
}
#[weight = 10_000]
pub fn reinit_bailsman(origin,
request: ReinitRequest<T::AccountId, T::BlockNumber>,
_signature: <T::AuthorityId as RuntimeAppPublic>::Signature) {
ensure_none(origin)?;
eq_log!(
"reinit_bailsman by {:?} with len {}",
request.authority_index,
request.validators_len);
T::BailsmanManager::reinit();
}
#[weight = 10_000]
pub fn reinit_external(origin, owner: <T as system::Trait>::AccountId) -> Result<(),DispatchError>
{
ensure_signed(origin)?;
#[allow(unused_must_use)]
{
Self::_reinit(&owner);
}
Ok(())
}
#[weight = 10_000]
pub fn set_now_millis_offset(origin, offset: u64) -> Result<(),DispatchError>
{
ensure_root(origin)?;
let current_offset = NowMillisOffset::get();
eq_ensure!(offset > current_offset, Error::<T>::InvalidOffset,
"{}:{}. Offset to set is lower than current. Offset: {:?}, current offset: {:?}.",
file!(), line!(), offset, current_offset);
NowMillisOffset::put(offset);
eq_log!("Time offset set to {} seconds", offset / 1000);
Ok(())
}
fn offchain_worker(now: T::BlockNumber) {
const LOCKED: () = ();
if sp_io::offchain::is_validator() {
let key = DB_PREFIX.to_vec();
let mut storage = StorageValueRef::persistent(&key);
let can_process = storage.mutate(|is_locked: Option<Option<bool>>| {
match is_locked {
Some(Some(true)) => {
Err(LOCKED)
},
_ => Ok(true)
}
});
match can_process {
Ok(Ok(true)) => {
for res in Self::check_accounts(now).ok().unwrap() {
match res {
Ok(_) => {
},
Err(_) => {
},
}
}
storage.clear();
}
_ => {
}
}
} else {
debug::trace!(
target: "eqrate",
"Skipping reinit at {:?}. Not a validator.",
now,
)
}
}
}
}
impl<T: Trait> Module<T> {
fn initialize_keys(keys: &[T::AuthorityId]) {
if !keys.is_empty() {
assert!(Keys::<T>::get().is_empty(), "Keys are already initialized!");
Keys::<T>::put(keys);
}
}
#[cfg(test)]
#[allow(dead_code)]
fn set_keys(keys: Vec<T::AuthorityId>) {
Keys::<T>::put(&keys)
}
pub fn set_last_update(accounts: Vec<&T::AccountId>) {
let offset = NowMillisOffset::get();
let duration = core::time::Duration::from_millis(offset);
let now = T::UnixTime::now().as_secs() + duration.as_secs();
for account in accounts {
<LastFeeUpdate<T>>::insert(account, now);
}
}
fn check_accounts(
block_number: T::BlockNumber,
) -> OffchainResult<impl Iterator<Item = OffchainResult<()>>> {
let validators_len = <pallet_session::Module<T>>::validators().len() as u32;
let keys = Self::local_authority_keys();
let res = Ok(keys.map(move |(authority_index, key)| {
Self::check_accounts_for_single_auth(authority_index, key, block_number, validators_len)
}));
res
}
fn check_accounts_for_single_auth(
authority_index: u32,
key: T::AuthorityId,
block_number: T::BlockNumber,
validators_len: u32,
) -> OffchainResult<()> {
let bailman_acc_id: T::AccountId = T::BailsmanModuleId::get().into_account();
for (_, balance) in T::BalanceGetter::iterate_balances()
.iter()
.enumerate()
.filter(|(index, balance)| {
(*index as u32) % validators_len == authority_index && *balance.0 != bailman_acc_id
})
{
let last_update = <LastFeeUpdate<T>>::get(&balance.0);
let debt = match T::FeeManager::calc_fee(&balance.0, &last_update) {
Ok(d) => d,
Err(_) => continue,
};
let zero_balance = From::<u64>::from(0 as u64);
let change: SignedBalance<T::Balance> = SignedBalance::Positive(zero_balance);
let good_position =
T::LtvChecker::check_ltv(&balance.0, &change, &Currency::Usd).unwrap_or(true);
if debt.clone() > T::MinSurplus::get() || !good_position {
let reinit_data = ReinitRequest::<T::AccountId, T::BlockNumber> {
account: balance.0.clone(),
authority_index,
validators_len,
block_num: block_number,
};
eq_log!("try to reinit {:?}", balance.0.clone());
let option_signature = key.sign(&reinit_data.encode());
let signature = ok_or_error!(option_signature, OffchainErr::FailedSigning,
"{}:{}. Couldn't sign. Key: {:?}, request account: {:?}, authority_index: {:?}, validators_len: {:?}, block_num:{:?}.",
file!(), line!(), key, &reinit_data.account, &reinit_data.authority_index, &reinit_data.validators_len, &reinit_data.block_num)?;
let acc = reinit_data.account.clone();
let index = reinit_data.authority_index.clone();
let len = reinit_data.validators_len.clone();
let block = reinit_data.block_num.clone();
let sign = signature.clone();
let call = Call::reinit(reinit_data, signature);
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
.map_err(|_| {
debug::error!("{}:{}. Submit reinit error. Signature: {:?}, request account: {:?}, authority_index: {:?}, validators_len: {:?}, block_num:{:?}.",
file!(), line!(), sign, acc, index, len, block);
OffchainErr::SubmitTransaction
})?;
} else {
eq_log!("no need to reinit {:?}", balance.0.clone());
}
}
let accuracy = FixedI128::accuracy() / FixedI64::accuracy() as i128;
let bailsman_temp_balance = T::BailsmanManager::get_temp_balances_usd();
let min_aggregates =
FixedI128::from_inner(Into::<u64>::into(T::MinTempBailsman::get()) as i128 * accuracy);
let block_number_u = TryInto::<u64>::try_into(block_number).unwrap_or(0);
let is_my_block = (block_number_u) % (validators_len as u64) == authority_index as u64;
let need_to_reinit_bailsman = bailsman_temp_balance
.map(|balance| is_my_block && (balance.saturating_abs() > min_aggregates))
.unwrap_or(false);
if need_to_reinit_bailsman {
let reinit_data = ReinitRequest::<T::AccountId, T::BlockNumber> {
account: Default::default(),
authority_index,
validators_len,
block_num: block_number,
};
let option_signature = key.sign(&reinit_data.encode());
let signature = ok_or_error!(option_signature, OffchainErr::FailedSigning,
"{}:{}. Couldn't sign. Key: {:?}, request account: {:?}, authority_index: {:?}, validators_len: {:?}, block_num:{:?}.",
file!(), line!(), key, &reinit_data.account, &reinit_data.authority_index, &reinit_data.validators_len, &reinit_data.block_num)?;
let acc = reinit_data.account.clone();
let index = reinit_data.authority_index.clone();
let len = reinit_data.validators_len.clone();
let block = reinit_data.block_num.clone();
let sign = signature.clone();
let call = Call::reinit_bailsman(reinit_data, signature);
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
.map_err(|_| {
debug::error!("{}:{}. Submit reinit bailsman error. Signature: {:?}, request account: {:?}, authority_index: {:?}, validators_len: {:?}, block_num:{:?}.",
file!(), line!(), sign, acc, index, len, block);
OffchainErr::SubmitTransaction
})?;
}
Ok(())
}
fn local_authority_keys() -> impl Iterator<Item = (u32, T::AuthorityId)> {
let authorities = Keys::<T>::get();
let mut local_keys = T::AuthorityId::all();
local_keys.sort();
authorities
.into_iter()
.enumerate()
.filter_map(move |(index, authority)| {
local_keys
.binary_search(&authority)
.ok()
.map(|location| (index as u32, local_keys[location].clone()))
})
}
fn try_margincall(
owner: &<T as system::Trait>::AccountId,
can_margincall_good_position: bool,
) -> Result<(), DispatchError> {
let change: SignedBalance<T::Balance> = SignedBalance::zero();
let good_position = T::LtvChecker::check_ltv(&owner, &change, &Currency::Usd)?;
if good_position && !can_margincall_good_position {
return Err({
DispatchError::Other("This is a good position.")
});
}
eq_log!("margincall owner='{:?}' position.", owner);
T::BailsmanManager::receive_position(owner);
Ok(())
}
fn _reinit(owner: &<T as system::Trait>::AccountId) -> Result<(), Error<T>> {
let bailman_acc_id: T::AccountId = T::BailsmanModuleId::get().into_account();
if bailman_acc_id == *owner {
return Ok(());
}
eq_log!("REINIT!!!! {:?}", owner);
let last_update = <LastFeeUpdate<T>>::get(owner);
if Self::try_margincall(owner, false).is_ok() {
Self::set_last_update(vec![owner]);
return Ok(());
}
let debt = T::FeeManager::charge_fee(owner, &last_update);
#[allow(unused_must_use)]
if let Ok(_) = debt {
let current_eq_balance =
T::BalanceGetter::get_balance(owner, &eq_primitives::currency::Currency::Eq);
if let SignedBalance::Negative(negative_current_eq) = current_eq_balance {
eq_log!("buyout {:?}", negative_current_eq);
T::EqBuyout::eq_buyout(owner, negative_current_eq);
}
Self::set_last_update(vec![owner]);
Self::try_margincall(owner, false);
} else {
if last_update == 0 {
Self::set_last_update(vec![owner]);
}
}
Ok(())
}
}
impl<T: Trait> OnKilledAccount<T::AccountId> for Module<T> {
#[allow(unused_must_use)]
fn on_killed_account(who: &T::AccountId) {
Self::try_margincall(who, true);
}
}
impl<T: Trait> OnNewAccount<T::AccountId> for Module<T> {
fn on_new_account(who: &T::AccountId) {
Self::set_last_update(vec![who]);
}
}
impl<T: Trait> sp_runtime::BoundToRuntimeAppPublic for Module<T> {
type Public = T::AuthorityId;
}
impl<T: Trait> pallet_session::OneSessionHandler<T::AccountId> for Module<T> {
type Key = T::AuthorityId;
fn on_genesis_session<'a, I: 'a>(validators: I)
where
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
{
let keys = validators.map(|x| x.1).collect::<Vec<_>>();
Self::initialize_keys(&keys);
}
fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, _queued_validators: I)
where
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
{
Keys::<T>::put(validators.map(|x| x.1).collect::<Vec<_>>());
}
fn on_disabled(_i: usize) {
}
}
const INVALID_VALIDATORS_LEN: u8 = 10;
impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> {
type Call = Call<T>;
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
if let Call::reinit(request, signature) = call {
let keys = Keys::<T>::get();
if keys.len() as u32 != request.validators_len {
return InvalidTransaction::Custom(INVALID_VALIDATORS_LEN).into();
}
let authority_id = match keys.get(request.authority_index as usize) {
Some(id) => id,
None => return InvalidTransaction::BadProof.into(),
};
let signature_valid = request.using_encoded(|encoded_heartbeat| {
authority_id.verify(&encoded_heartbeat, &signature)
});
if !signature_valid {
return InvalidTransaction::BadProof.into();
}
ValidTransaction::with_tag_prefix("EqFee")
.priority(T::UnsignedPriority::get())
.and_provides((request.account.clone(), request.block_num))
.longevity(64)
.propagate(true)
.build()
} else if let Call::reinit_bailsman(request, signature) = call {
let keys = Keys::<T>::get();
if keys.len() as u32 != request.validators_len {
return InvalidTransaction::Custom(INVALID_VALIDATORS_LEN).into();
}
let authority_id = match keys.get(request.authority_index as usize) {
Some(id) => id,
None => return InvalidTransaction::BadProof.into(),
};
let signature_valid = request.using_encoded(|encoded_heartbeat| {
authority_id.verify(&encoded_heartbeat, &signature)
});
if !signature_valid {
return InvalidTransaction::BadProof.into();
}
ValidTransaction::with_tag_prefix("EqBails")
.priority(T::UnsignedPriority::get())
.and_provides(request.block_num)
.longevity(64)
.propagate(true)
.build()
} else {
InvalidTransaction::Call.into()
}
}
}
impl<T: Trait> UnixTime for Module<T> {
fn now() -> core::time::Duration {
let offset = NowMillisOffset::get();
let duration = core::time::Duration::from_millis(offset);
let now = T::UnixTime::now();
now + duration
}
}